Могу ли я реализовать на C ++ автономный тип члена self?

101

В C ++ отсутствует эквивалент ключевого слова PHPself , которое оценивает тип включающего класса.

Достаточно легко подделать это для каждого класса:

struct Foo
{
   typedef Foo self;
};

но мне пришлось писать Fooснова. Возможно, однажды я сделаю это неправильно и вызову тихую ошибку.

Могу ли я использовать комбинацию из decltypeи друзей, чтобы эта работа работала "автономно"? Я уже пробовал следующее, но thisв этом месте недопустимо:

struct Foo
{
   typedef decltype(*this) self;
};

// main.cpp:3:22: error: invalid use of 'this' at top level
//     typedef decltype(*this) self;

(Я не буду беспокоиться об эквиваленте static, который делает то же самое, но с поздним связыванием.)

Гонки легкости на орбите
источник
9
this_tвероятно, будет больше соответствовать обычному именованию C ++.
Bartek Banachewicz
3
@BartekBanachewicz: or this_type
PlasmaHH
10
@Praetorian, я не могу вспомнить, было ли это предложение или нет, но кто-то предложил auto()и ~auto()для ctors / dtors. По меньшей мере интересно. Возможно typedef auto self;, если использовать для этой цели, но мне это кажется немного схематичным.
Крис
11
Честно говоря, если бы я собирался предложить синтаксис, чтобы сделать это возможным, я бы, вероятно, был decltype(class), возможно, с decltype(struct)эквивалентом. Это намного яснее, чем просто autoв конкретном контексте, и я не вижу никаких проблем с тем, чтобы это соответствовало языку, основанному на decltype(auto).
Крис
11
Поскольку вы хотите избежать ошибок, вы можете настроить фиктивную функцию-член с помощью static_assert, например, void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }не работает с шаблонами классов, хотя ...
milleniumbug

Ответы:

39

Вот как это можно сделать, не повторяя тип Foo:

template <typename...Ts>
class Self;

template <typename X, typename...Ts>
class Self<X,Ts...> : public Ts...
{
protected:
    typedef X self;
};

#define WITH_SELF(X) X : public Self<X>
#define WITH_SELF_DERIVED(X,...) X : public Self<X,__VA_ARGS__>

class WITH_SELF(Foo)
{
    void test()
    {
        self foo;
    }
};

Если вы хотите наследовать, Fooвы должны использовать макрос WITH_SELF_DERIVEDследующим образом:

class WITH_SELF_DERIVED(Bar,Foo)
{
    /* ... */
};

Вы даже можете выполнять множественное наследование с любым количеством базовых классов (благодаря вариативным шаблонам и вариативным макросам):

class WITH_SELF(Foo2)
{
    /* ... */
};

class WITH_SELF_DERIVED(Bar2,Foo,Foo2)
{
    /* ... */
};

Я проверил, что это работает на gcc 4.8 и clang 3.4.

Ральф Тандецки
источник
18
Думаю, ответ - "нет, но Ральф может!" ;)
Lightness Races in Orbit
3
Чем это лучше, чем просто поместить туда typedef? И боже, зачем вам вообще typedef? Зачем?
Miles Rout
7
@MilesRout Это вопрос о вопросе, а не о ответе. Во многих случаях при разработке программного обеспечения (и особенно при его обслуживании) полезно избегать дублирования кода, чтобы изменение чего-либо в одном месте не потребовало от вас изменения кода в другом месте. В этом весь смысл autoи decltypeили в этом случае self.
Ральф Тандецки
1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};было бы проще и позволило бы более точно контролировать наследование - есть ли причины против?
Аконкагуа
@mmmmmmmm, если вы не научились глубоко ценить принцип «Не повторяйся», скорее всего, вы еще недостаточно / серьезно написали код. Этот «беспорядок» (на самом деле далеко не так) - довольно элегантное исправление в контексте разговора о неэлегантной языковой особенности (или неправильной функции, даже недостатке по некоторым строгим мерам).
Sz.
38

Возможный обходной путь (поскольку вам все равно нужно написать тип один раз):

template<typename T>
struct Self
{
protected:
    typedef T self;
};

struct Foo : public Self<Foo>
{
    void test()
    {
        self obj;
    }
};

Для более безопасной версии мы могли бы гарантировать, что она Tдействительно происходит из Self<T>:

Self()
{
    static_assert(std::is_base_of<Self<T>, T>::value, "Wrong type passed to Self");
}

Обратите внимание, что static_assertвнутренняя функция-член, вероятно, является единственным способом проверки, поскольку передаваемые типы std::is_base_ofдолжны быть полными.

Себастьян Хоффманн
источник
4
Нет необходимости typenameв typedef. И поскольку это не уменьшает количество дублирующих элементов, я не думаю, что это жизнеспособная альтернатива.
Конрад Рудольф
У него точно такая же проблема с повторением Fooимени.
Bartek Banachewicz
6
Однако это немного лучше, чем исходный подход, поскольку повторения очень близки друг к другу. Не решение вопроса, а +1 за достойную попытку лучшего обходного пути.
Гонки легкости на орбите
4
Я использовал это решение пару раз, и у него есть одна ПЛОХАЯ вещь: при последующем получении от Fooвас нужно либо: (1) распространить T вверх на лист-потомок, либо (2) не забыть наследовать от SelfT много раз , или (3) принять, что все дети должны быть Базой ... пригодной для использования, но некрасивой.
quetzalcoatl
@quetzalcoatl: Поскольку я пытаюсь воспроизвести, selfа не static, это не проблема.
Lightness Races in Orbit
33

Вы можете использовать макрос вместо обычного объявления класса, который сделает это за вас.

#define CLASS_WITH_SELF(X) class X { typedef X self;

А затем используйте как

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; вероятно, улучшит читаемость.


Вы также можете взять @ Paranaix's Selfи использовать его (он начинает становиться действительно хакерским)

#define WITH_SELF(X) X : public Self<X>

class WITH_SELF(Foo) {
};
Бартек Баначевич
источник
18
EWWWW END_CLASS. Это совершенно не нужно.
Puppy
31
@DeadMG Я думаю, кому-то понравится больше согласованности; в конце концов, первое использование макроса не заканчивается {, поэтому }он "зависает", что, вероятно, тоже не понравится текстовым редакторам.
Bartek Banachewicz
6
Хорошая идея, но даже при том, что я принципиально не против макросов, я бы согласился с их использованием здесь только в том случае, если бы они имитировали область видимости C ++, то есть если бы их можно было использовать как CLASS_WITH_SELF(foo) { … };- а я думаю, что этого невозможно достичь.
Конрад Рудольф
2
@KonradRudolph Я тоже добавил способ сделать это. Не то чтобы мне это нравилось, просто для полноты картины
Бартек Баначевич
1
Однако у этого подхода есть некоторые проблемы. Первый не позволяет вам легко наследовать класс (если вы не используете другой параметр (ы) макроса), а второй имеет все проблемы наследования от него, который Selfимеет.
Bartek Banachewicz
31

У меня нет убедительных доказательств, но я думаю, что это невозможно. Следующее не удается - по той же причине, что и ваша попытка - и я думаю, что это максимум, что мы можем сделать:

struct Foo {
    auto self_() -> decltype(*this) { return *this; }

    using self = decltype(self_());
};

По сути, это демонстрирует, что область, в которой мы хотим объявить наш typedef, просто не имеет доступа (будь то прямой или косвенный) this, и нет другого (независимого от компилятора) способа добраться до типа или имени класса.

Конрад Рудольф
источник
4
Возможно ли это с выводом типа возвращаемого значения C ++ 1y?
dyp
4
@dyp Для целей моего ответа это ничего не изменит. Ошибка здесь не в конечном возвращаемом типе, а в вызове.
Конрад Рудольф
1
@quetzalcoatl: Внутренности decltype- это неоцененный контекст, поэтому вызов функции-члена не проблема (это не будет предприниматься)
Гонки легкости на орбите
1
@TomKnapen Попробуйте использовать clang, и ничего не получится. Тот факт, что он принят GCC, является ошибкой, насколько мне известно.
4
FWIW struct S { int i; typedef decltype(i) Int; };работает, даже если iявляется нестатическим членом данных. Это работает, потому что decltypeесть специальное исключение, когда простое имя не оценивается как выражение. Но я не могу придумать, как использовать эту возможность для ответа на этот вопрос.
21

Что работает как в GCC, так и в clang, так это создание typedef, на который ссылается this, используя thisв завершающем-возвращаемом-типе функции typedef. Поскольку это не объявление статической функции-члена, использование thisдопускается. Затем вы можете использовать этот typedef для определения self.

#define DEFINE_SELF() \
    typedef auto _self_fn() -> decltype(*this); \
    using self = decltype(((_self_fn*)0)())

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

К сожалению, строгое прочтение стандарта говорит о том, что даже это неверно. Что делает clang, так это проверка, которая thisне используется в определении статической функции-члена. А здесь это действительно не так. GCC не возражает, если thisон используется в конечном возвращаемом типе, независимо от типа функции, он позволяет это даже для staticфункций-членов. Однако на самом деле стандарт требует, чтобы thisэто не использовалось за пределами определения нестатической функции-члена (или инициализатора нестатического элемента данных). Intel понимает это правильно и отвергает это.

При условии:

  • this разрешено только в инициализаторах нестатических элементов данных и нестатических функциях-членах ([expr.prim.general] p5),
  • нестатические элементы данных не могут быть выведены из инициализатора ([dcl.spec.auto] p5),
  • на нестатические функции-члены можно ссылаться только по неполному имени в контексте вызова функции ([expr.ref] p4)
  • нестатические функции-члены могут вызываться только по неполному имени, даже в неоцененных контекстах, когда thisих можно использовать ([over.call.func] p3),
  • ссылка на нестатическую функцию-член по квалифицированному имени или доступу к члену требует ссылки на определяемый тип

Я думаю, что могу окончательно сказать, что нет никакого способа реализовать selfбез включения каким-либо образом, где-то, имени типа.

Изменить : в моих предыдущих рассуждениях есть изъян. «нестатические функции-члены могут вызываться только по неквалифицированному имени, даже в неоцененных контекстах, когда это можно использовать ([over.call.func] p3)» неверно. На самом деле он говорит

Если ключевое слово this(9.3.2) находится в области видимости и относится к классу Tили производному классу T, то подразумеваемым аргументом объекта является (*this). Если ключевое слово thisне входит в область видимости или относится к другому классу, то надуманный объект типа Tстановится подразумеваемым аргументом объекта. Если список аргументов дополняется надуманным объектом и при разрешении перегрузки выбирается одна из нестатических функций-членов T, вызов имеет неправильный формат.

Внутри статической функции-члена thisможет не отображаться, но она все еще существует.

Однако, согласно комментариям, внутри статической функции-члена преобразование f()в (*this).f()не будет выполняться, и если это не выполняется, то [expr.call] p1 нарушается:

[...] Для вызова функции-члена постфиксное выражение должно быть неявным (9.3.1, 9.4) или явным доступом к члену класса (5.2.5), [...]

так как не будет доступа для членов. Так что даже это не сработает.


источник
Я думаю, что [class.mfct.non-static] / 3 говорит, что _self_fn_1()"преобразовано" в (*this)._self_fn_1(). Не уверен, что это делает его незаконным.
dyp
@dyp Он говорит: «используется в члене класса Xв контексте, в котором thisего можно использовать», поэтому я не думаю, что преобразование выполняется.
1
Но тогда это не явный и неявный доступ к члену класса ..? [expr.call] / 1 «Для вызова функции-члена постфиксное выражение должно быть неявным или явным доступом к члену класса [...]»
dyp
(Я имею в виду, что происходит, когда у вас есть auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp
@dyp [expr.call] / 1 - хороший аргумент, мне нужно будет присмотреться. А вот насчет constперегрузок: это не проблема. 5.1p3 был специально изменен, чтобы также применяться к статическим функциям-членам, и говорит, что тип thisis Foo*/ Bar*(без const), потому что constв объявлении нет _self_fn_2.
17
#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::type, SELF >::value, "self wrong type" ); }
#define SELF(T) typedef T self; SELF_CHECK(T)

struct Foo {
  SELF(Foo); // works, self is defined as `Foo`
};
struct Bar {
  SELF(Foo); // fails
};

это не работает с типами шаблонов, так как self_checkне вызывается, поэтому static_assertне оценивается.

Мы можем сделать несколько приемов, чтобы заставить его работать для templates, но это требует незначительных затрат времени выполнения.

#define TESTER_HELPER_TYPE \
template<typename T, std::size_t line> \
struct line_tester_t { \
  line_tester_t() { \
    static_assert( std::is_same< decltype(T::line_tester), line_tester_t<T,line> >::value, "test failed" ); \
    static_assert( std::is_same< decltype(&T::static_test_zzz), T*(*)() >::value, "test 2 failed" ); \
  } \
}

#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::type, SELF >::value, "self wrong type" ); }

#define SELF(T) typedef T self; SELF_CHECK(T); static T* static_test_zzz() { return nullptr; }; TESTER_HELPER_TYPE; line_tester_t<T,__LINE__> line_tester

structв вашем классе создается пустой размер 1 байт. Если ваш тип создан, selfпроверяется.

Якк - Адам Неврамонт
источник
Это тоже неплохо!
Гонки легкости на орбите
@LightnessRacesinOrbit теперь с templateвариантами поддержки классов.
Yakk - Адам Неврамонт
Я думал именно об этом, когда уходил с работы вчера. Ты подтолкнул меня на это :). Я бы предложил объявить self_check () встроенным, чтобы избежать проблем с связыванием (тот же символ Foo :: self_check (), найденный в нескольких объектных файлах).
the swine
1
@theswine: 9,3 / 2 является индекс пункта в стандарте C ++, который гарантирует , что функции - члены класса , определенные в теле определения класса уже, неявно inline. Это означает, что вам вообще не нужно писать inline. Так что, если вы писали inlineперед каждым таким определением функции-члена класса на протяжении всей своей карьеры, вы можете остановиться сейчас;)
Lightness Races in Orbit
2
@LightnessRacesinOrbit О, на самом деле я был. Спасибо, это избавит меня от необходимости печатать в будущем :). Меня всегда удивляло, как много я не знаю о C ++.
the swine
11

Я тоже думаю, что это невозможно, вот еще одна неудачная, но ИМХО интересная попытка, которая позволяет избежать this-access:

template<typename T>
struct class_t;

template<typename T, typename R>
struct class_t< R (T::*)() > { using type = T; };

struct Foo
{
   void self_f(); using self = typename class_t<decltype(&self_f)>::type;
};

#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo >::value, "" );
}

что не удается, потому что C ++ требует, чтобы вы self_fсоответствовали классу, когда хотите получить его адрес :(

Дэниел Фрей
источник
И та же проблема возникает с обычным int T::*указателем на переменную-член. И int self_var; typedef decltype(&self_var) self_ptrтоже не работает, это обычное дело int*.
MSalters
9

Недавно я обнаружил, что *thisэто разрешено в инициализаторе фигурной скобки или равного . Описано в п. 5.1.1 ( из рабочего проекта n3337 ):

3 [..] В отличие от объектного выражения в других контекстах, *thisне обязательно иметь полный тип для целей доступа к членам класса (5.2.5) вне тела функции-члена. [..]

4 В противном случае, если член-декларатор объявляет нестатический член данных (9.2) класса X, выражение thisявляется prvalue типа «указатель на X» в необязательном инициализаторе фигурной скобки или равного . Он не должен появляться где-либо еще в заявителе участника .

5 Выражение thisне должно появляться ни в каком другом контексте. [ Пример:

class Outer {
    int a[sizeof(*this)];               // error: not inside a member function
    unsigned int sz = sizeof(*this);    // OK: in brace-or-equal-initializer

    void f() {
        int b[sizeof(*this)];           // OK
        struct Inner {
            int c[sizeof(*this)];       // error: not inside a member function of Inner
        };
    }
};

- конец примера ]

Имея это в виду, следующий код:

struct Foo
{
    Foo* test = this;
    using self = decltype(test);

    static void smf()
    {
        self foo;
    }
};

#include <iostream>
#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo* >::value, "" );
}

проходит Даниэль Фрей static_assert .

Live example

Сообщество
источник
У вас есть раздражающий бесполезный переменные , testхотя
MM
@Matt Верно, но мне все равно было интересно.
1
Это могло бы сработать без = this, не так ли? И почему не простоusing self = Foo*;
user362515
1
Мы, конечно, ничего здесь не получили, потому что мы должны были объявить, что принадлежим testк типу, ммм Foo *,!
Пол Сандерс
4

Если тип не должен быть тип члена вмещающего класса можно заменить использование selfс decltype(*this). Если вы используете его во многих местах своего кода, вы можете определить макрос SELFследующим образом:

#define SELF decltype(*this)
ТАС
источник
2
И вы не можете использовать это вне класса или во вложенных классах
Дракс
1
@Drax: Он не должен быть доступен вне класса.
Ben Voigt
@BenVoigt Но предполагается, что он будет доступен во вложенных классах, что является наиболее интересным вариантом использования IMO.
Drax
1
Я так не думаю. Не следует selfссылаться на непосредственно включающий класс, а не на внешний класс? Но я не так хорошо знаю php.
Ben Voigt
1
@LightnessRacesinOrbit: Я предполагаю, что код и ошибка должны говорить: «PHP не имеет вложенных типов»?
Ben Voigt
1

Приведите мою версию. Лучше всего то, что он используется так же, как собственный класс. Однако это не работает для классов шаблонов.

template<class T> class Self;

#define CLASS(Name) \
class Name##_; \
typedef Self<Name##_> Name; \
template<> class Self<Name##_>

CLASS(A)
{
    int i;
    Self* clone() const { return new Self(*this); }
};

CLASS(B) : public A
{
    float f;
    Self* clone() const { return new Self(*this); }
};
user1899020
источник
1

Основываясь на ответе hvd, я обнаружил, что единственное, чего не хватало, - это удаление ссылки, поэтому проверка std :: is_same не выполняется (b / c результирующий тип фактически является ссылкой на тип). Теперь этот макрос без параметров может делать всю работу. Рабочий пример ниже (я использую GCC 8.1.1).

#define DEFINE_SELF \
    typedef auto _self_fn() -> std::remove_reference<decltype(*this)>::type; \
    using self = decltype(((_self_fn*)0)())

class A {
    public:
    DEFINE_SELF;
};

int main()
{
    if (std::is_same_v<A::self, A>)
        std::cout << "is A";
}
Никсбеник
источник
Он не компилируется на других компиляторах, кроме GCC.
zedu
0

Я повторю очевидное решение «делать самому». Это краткая версия кода C ++ 11, которая работает как с простыми классами, так и с шаблонами классов:

#define DECLARE_SELF(Type) \
    typedef Type TySelf; /**< @brief type of this class */ \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<decltype(*((TySelf*)(0))), \
            decltype(*this)>::value, "TySelf is not what it should be"); \
    } \
    enum { static_self_check_token = __LINE__ }; \
    static_assert(int(static_self_check_token) == \
        int(TySelf::static_self_check_token), \
        "TySelf is not what it should be")

Вы можете увидеть это в действии на ideone . Ниже приводится генезис, приведший к такому результату:

#define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */

struct XYZ {
    DECLARE_SELF(XYZ)
};

Это имеет очевидную проблему с копированием кода в другой класс и забыванием изменить XYZ, как здесь:

struct ABC {
    DECLARE_SELF(XYZ) // !!
};

Мой первый подход был не очень оригинальным - я сделал такую ​​функцию:

/**
 *  @brief namespace for checking the _TySelf type consistency
 */
namespace __self {

/**
 *  @brief compile-time assertion (_TySelf must be declared the same as the type of class)
 *
 *  @tparam _TySelf is reported self type
 *  @tparam _TyDecltypeThis is type of <tt>*this</tt>
 */
template <class _TySelf, class _TyDecltypeThis>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 *  @tparam _TySelf is reported self type (same as type of <tt>*this</tt>)
 */
template <class _TySelf>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TySelf, _TySelf> {};

/**
 *  @brief static assertion helper type
 *  @tparam n_size is size of object being used as assertion message
 *      (if it's a incomplete type, compiler will display object name in error output)
 */
template <const size_t n_size>
class CStaticAssert {};

/**
 *  @brief helper function for self-check, this is used to derive type of this
 *      in absence of <tt>decltype()</tt> in older versions of C++
 *
 *  @tparam _TyA is reported self type
 *  @tparam _TyB is type of <tt>*this</tt>
 */
template <class _TyA, class _TyB>
inline void __self_check_helper(_TyB *UNUSED(p_this))
{
    typedef CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TyA, _TyB>)> _TyAssert;
    // make sure that the type reported as self and type of *this is the same
}

/**
 *  @def __SELF_CHECK
 *  @brief declares the body of __self_check() function
 */
#define __SELF_CHECK \
    /** checks the consistency of _TySelf type (calling it has no effect) */ \
    inline void __self_check() \
    { \
        __self::__self_check_helper<_TySelf>(this); \
    }

/**
 *  @def DECLARE_SELF
 *  @brief declares _TySelf type and adds code to make sure that it is indeed a correct one
 *  @param[in] Type is type of the enclosing class
 */
#define DECLARE_SELF(Type) \
    typedef Type _TySelf; /**< @brief type of this class */ \
    __SELF_CHECK

} // ~self

Это довольно долго, но, пожалуйста, будьте терпеливы. Это имеет то преимущество, что работает в C ++ 03 без него decltype, поскольку__self_check_helper функция используется для определения типа this. Также нет static_assert, но sizeof()вместо этого используется трюк. Вы могли бы сделать его намного короче для C ++ 0x. Теперь это не будет работать с шаблонами. Кроме того, есть небольшая проблема с макросом, не ожидающим точки с запятой в конце, при компиляции с педантичностью он будет жаловаться на лишнюю ненужную точку с запятой (или вы останетесь с макросом странного вида, не оканчивающимся точкой с запятой в теле XYZи ABC).

Проверяя Type что передано, DECLARE_SELFне является вариантом, поскольку это будет проверять только XYZкласс (что нормально), не обращая внимания ABC(который имеет ошибку). И тут меня осенило. Бесплатное решение без дополнительных хранилищ, работающее с шаблонами:

namespace __self {

/**
 *  @brief compile-time assertion (_TySelf must be declared the same as the type of class)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 */
template <>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<true> {};

/**
 *  @def DECLARE_SELF
 *  @brief declares _TySelf type and adds code to make sure that it is indeed a correct one
 *  @param[in] Type is type of the enclosing class
 */
#define DECLARE_SELF(Type) \
    typedef Type _TySelf; /**< @brief type of this class */ \
    __SELF_CHECK \
    enum { __static_self_check_token = __LINE__ }; \
    typedef __self::CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<int(__static_self_check_token) == int(_TySelf::__static_self_check_token)>)> __static_self_check

} // ~__self 

Это просто делает статическое утверждение на уникальном значении перечисления (или, по крайней мере, уникальное, если вы не пишете весь свой код в одной строке), никаких уловок сравнения типов не используется, и оно работает как статическое утверждение, даже в шаблонах . И как бонус - теперь обязательная точка с запятой :).

Я хотел бы поблагодарить Yakk за хорошее вдохновение. Я бы не стал писать это, не увидев сначала его ответа.

Протестировано с VS 2008 и g ++ 4.6.3. Действительно, с помощью XYZиABC , например, он жалуется:

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp:91:5: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<false>â
self.cpp:91:5: error: template argument 1 is invalid
self.cpp: In function âvoid __self::__self_check_helper(_TyB*) [with _TyA = XYZ, _TyB = ABC]â:
self.cpp:91:5:   instantiated from here
self.cpp:58:87: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<XYZ, ABC

Теперь, если мы сделаем ABC шаблоном:

template <class X>
struct ABC {
    DECLARE_SELF(XYZ); // line 92
};

int main(int argc, char **argv)
{
    ABC<int> abc;
    return 0;
}

Мы получим:

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp: In instantiation of âABC<int>â:
self.cpp:97:18:   instantiated from here
self.cpp:92:9: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<false>â

Сработала только проверка номера строки, поскольку проверка функции не была скомпилирована (как ожидалось).

С C ++ 0x (и без злого подчеркивания) вам понадобится всего:

namespace self_util {

/**
 *  @brief compile-time assertion (tokens in class and TySelf must match)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 */
template <>
class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<true> {};

/**
 *  @brief static assertion helper type
 *  @tparam n_size is size of object being used as assertion message
 *      (if it's a incomplete type, compiler will display object name in error output)
 */
template <const size_t n_size>
class CStaticAssert {};

#define SELF_CHECK \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<TySelf, decltype(*this)>::value, "TySelf is not what it should be"); \
    }

#define DECLARE_SELF(Type) \
    typedef Type TySelf; /**< @brief type of this class */ \
    SELF_CHECK \
    enum { static_self_check_token = __LINE__ }; \
    typedef self_util::CStaticAssert<sizeof(SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<int(static_self_check_token) == int(TySelf::static_self_check_token)>)> static_self_check

} // ~self_util

Я считаю, что бит CStaticAssert, к сожалению, все еще требуется, поскольку он создает тип, который определяется типом в теле шаблона (я полагаю, с этим нельзя сделать то же самое static_assert). Преимуществом этого подхода по-прежнему является его нулевая стоимость.

свинья
источник
По сути, вы здесь заново реализуете static_assert, не так ли? Кроме того, ваш полный код недействителен, потому что вы используете недопустимые (зарезервированные) идентификаторы.
Конрад Рудольф
@KonradRudolph Да, это действительно так. В то время у меня нет C ++ 0x, поэтому я повторно реализовал static_assert, чтобы дать полный ответ. Я говорю это в ответ. Это недействительно? Вы можете указать как? Скомпилировано нормально, сейчас пользуюсь.
the swine
1
Идентификаторы недействительны, поскольку C ++ резервирует все с ведущим подчеркиванием, за которым следует заглавная буква, а также два ведущих подчеркивания в глобальной области для компилятора. Код пользователя не должен его использовать, но не все компиляторы помечают это как ошибку.
Конрад Рудольф
@KonradRudolph Понятно, я этого не знал. У меня много кода, который использует это, никогда не было проблем с ним ни на Linux / Mac / Windows. Но я думаю, это хорошо знать.
the swine
0

Я не знаю всего об этих дурацких шаблонах, как насчет чего-то сверхпростого:

#define DECLARE_TYPEOF_THIS typedef CLASSNAME typeof_this
#define ANNOTATED_CLASSNAME(DUMMY) CLASSNAME

#define CLASSNAME X
class ANNOTATED_CLASSNAME (X)
{
public:
    DECLARE_TYPEOF_THIS;
    CLASSNAME () { moi = this; }
    ~CLASSNAME () { }
    typeof_this *moi;
    // ...
};    
#undef CLASSNAME

#define CLASSNAME Y
class ANNOTATED_CLASSNAME (Y)
{
    // ...
};
#undef CLASSNAME

Работа сделана, если только вы не переносите пару макросов. Вы даже можете использовать CLASSNAMEдля объявления своего конструктора (ов) (и, конечно же, деструктора).

Живая демонстрация .

Пол Сандерс
источник
1
Это очень сильно влияет на то, как можно / нужно использовать класс
Гонки
@LightnessRacesinOrbit Как так? Я этого не вижу. Поразмыслив, я удалил последнее предложение своего исходного сообщения. То, что у меня было изначально, могло заставить вас так подумать.
Пол Сандерс