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

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

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

 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 , который делает то же самое, но с поздним связыванием).

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

 template  class Self; template  class Self : public Ts... { protected: typedef X self; }; #define WITH_SELF(X) X : public Self #define WITH_SELF_DERIVED(X,...) X : public Self class WITH_SELF(Foo) { void test() { self foo; } }; 

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

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

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

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

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

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

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

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

 CLASS_WITH_SELF(Foo) }; 

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


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

 #define WITH_SELF(X) X : public Self class WITH_SELF(Foo) { }; 

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

 template struct Self { protected: typedef T self; }; struct Foo : public Self { void test() { self obj; } }; 

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

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

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

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

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

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

То, что работает как в 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 не возражает, если он используется в типе возвращаемого типа, независимо от вида функции, он позволяет использовать его даже для 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) находится в области видимости и относится к classу T или производному classу T , то подразумеваемый аргумент объекта (*this) . Если ключевое слово this не в области видимости или относится к другому classу, то надуманный объект типа T становится аргументом объекта подразумеваемого объекта. Если список аргументов дополняется надуманным объектом, а разрешение перегрузки выбирает одну из нестатических функций-членов T , вызов плохо сформирован.

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

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

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

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

 #define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay::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 не оценивается.

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

 #define TESTER_HELPER_TYPE \ template \ struct line_tester_t { \ line_tester_t() { \ static_assert( std::is_same< decltype(T::line_tester), line_tester_t >::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::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 line_tester 

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

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

 template struct class_t; template struct class_t< R (T::*)() > { using type = T; }; struct Foo { void self_f(); using self = typename class_t::type; }; #include  int main() { static_assert( std::is_same< Foo::self, Foo >::value, "" ); } 

который не выполняется, потому что C ++ требует, чтобы вы квалифицировали self_f с classом, когда хотите принять его адрес 🙁

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

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

4 В противном случае, если член-декларатор объявляет нестатический член данных (9.2) classа X, выражение this значение значения типа «указатель на 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  #include  int main() { static_assert( std::is_same< Foo::self, Foo* >::value, "" ); } 

передает static_assert Дэниела Фрея .

Живой пример

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

 #define SELF decltype(*this) 

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

 template class Self; #define CLASS(Name) \ class Name##_; \ typedef Self Name; \ template<> class Self CLASS(A) { int i; Self* clone() const { return new Self(*this); } }; CLASS(B) : public A { float f; Self* clone() const { return new Self(*this); } }; 

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

 #define DEFINE_SELF \ typedef auto _self_fn() -> std::remove_reference::type; \ using self = decltype(((_self_fn*)0)()) class A { public: DEFINE_SELF; }; int main() { if (std::is_same_v) std::cout << "is A"; } 

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

 #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::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") 

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

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

Это имеет очевидную проблему с копированием кода в другой class и забыванием изменить 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 *this */ template  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 *this) */ template  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  class CStaticAssert {}; /** * @brief helper function for self-check, this is used to derive type of this * in absence of decltype() in older versions of C++ * * @tparam _TyA is reported self type * @tparam _TyB is type of *this */ template  inline void __self_check_helper(_TyB *UNUSED(p_this)) { typedef CStaticAssert)> _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 используется для вывода этого типа. Кроме того, static_assert не static_assert , но вместо этого используется трюк sizeof() . Вы можете сделать это намного короче для C ++ 0x. Теперь это не будет работать для шаблонов. Кроме того, есть небольшая проблема с тем, что макрос не ожидает точку с запятой в конце, если компилируется с педантичным, он будет жаловаться на лишнюю ненужную точку с запятой (или вы останетесь с нечетным макросом, не заканчивающимся точкой с запятой в теле XYZ и ABC ).

Выполнение проверки Type , переданного в DECLARE_SELF , не является вариантом, так как это будет проверять только class 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  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 {}; /** * @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)> __static_self_check } // ~__self 

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

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

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

 [email protected]:~$ 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â 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â 

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

 template  struct ABC { DECLARE_SELF(XYZ); // line 92 }; int main(int argc, char **argv) { ABC abc; return 0; } 

Мы получим:

 [email protected]:~$ g++ self.cpp -c -o self.o self.cpp: In instantiation of âABCâ: 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â 

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

С C ++ 0x (и без злых подчеркиваний) вам нужно просто:

 namespace self_util { /** * @brief compile-time assertion (tokens in class and TySelf must match) * @tparam b_check is the asserted value */ template  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 {}; /** * @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  class CStaticAssert {}; #define SELF_CHECK \ /** checks the consistency of TySelf type (calling it has no effect) */ \ void self_check() \ { \ static_assert(std::is_same::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)> static_self_check } // ~self_util 

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

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

 #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 для объявления своего конструктора (и, конечно же, деструктора).

Демо-версия .

  • Каковы правильные параметры ссылок для использования std :: thread в GCC под linux?
  • Разница между указателем и ссылкой как параметр streamа
  • неclassовые значения всегда имеют cv-неквалифицированные типы
  • Должен ли я использовать shared_ptr или unique_ptr
  • Использование libstdc ++ скомпилированных библиотек с помощью clang ++ -stdlib = libc ++
  • ошибка C2679: двоичный '<<': оператор не найден, который принимает правый операнд типа 'std :: string' (или нет приемлемого преобразования)
  • Переместить оператор присваивания и `if (this! = & Rhs)`
  • Является ли это C ++ 11 regex ошибкой или компилятором?
  • Является ли cout синхронизированным / streamобезопасным?
  • Это бесконечная recursion UB?
  • Можно ли использовать std :: string в constexpr?
  • Давайте будем гением компьютера.