Могу ли я реализовать автономный тип `self` в С++? - программирование
Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

Здесь вы можете сделать это, не повторяя тип 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.

Ответ 2

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

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, должны быть завершены.

Ответ 3

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

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

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

CLASS_WITH_SELF(Foo) 
};

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


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

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

class WITH_SELF(Foo) {
};

Ответ 4

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

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

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

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

Ответ 5

Что работает как в 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 используется в типе trailing-return-type независимо от вида функции, он допускает его даже для функций-членов 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), чей [...]

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

Ответ 6

#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 не оценивается.

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

#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 проверяется.

Ответ 7

Я также думаю, что это невозможно, здесь другая неудачная, но IMHO интересная попытка, которая позволяет избежать 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, "" );
}

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

Ответ 8

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

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

4 В противном случае, если член-декларатор объявляет нестатические данные член (9.2) класса 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 <iostream>
#include <type_traits>

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

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

Пример в реальном времени

Ответ 9

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

#define SELF decltype(*this)

Ответ 10

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

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); }
};

Ответ 11

Я повторю очевидное решение "делать это самостоятельно". Это краткая версия кода С++ 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 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

Это довольно длинный, но, пожалуйста, несите меня здесь. Это имеет то преимущество, что работает на С++ 03 без decltype, так как функция __self_check_helper используется для вывода типа this. Кроме того, нет static_assert, но вместо этого используется трюк sizeof(). Вы можете сделать это намного короче для С++ 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 

Это просто делает статическое утверждение уникальным значением перечисления (или, по крайней мере, уникальным, если вы не пишете весь свой код в одной строке), ни один метод сравнения типов не используется, и он работает как 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<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;
}

Мы получим:

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

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

С С++ 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 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). Преимущество этого подхода по-прежнему сводится к нулю.