Чи можу я реалізувати автономний тип "self" в C ++?


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: або this_type
ПлазмаHH

10
@Praetorian, я не можу пригадати, була це пропозиція чи ні, але хтось запропонував auto()і ~auto()для ctors / dtors. Цікаво сказати щонайменше. Якщо використовується для цієї мети, можливо typedef auto self;, але це здається мені трохи схематичним.
chris

11
Чесно кажучи, якби я збирався запропонувати синтаксис, щоб зробити це можливим, це було б decltype(class), можливо, з decltype(struct)еквівалентом. Це набагато зрозуміліше, ніж просто autoв конкретному контексті, і я не бачу жодних проблем з його вписуванням у мову, засновану на мові decltype(auto).
chris

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
Я здогадуюсь відповідь "ні, але Ральф може!" ;)
Гонки легкості на орбіті

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
Я використав це рішення кілька разів, і він має одну BAD річ: коли пізніше виходить Foo, ви повинні: (1) розповсюджувати T вгору до нащадка, або (2) не забувати успадковувати від SelfT багато разів або (3) прийняти, що всі діти повинні бути базовими .. корисними, але непоказними.
quetzalcoatl

@quetzalcoatl: Оскільки я намагаюся скопіювати, selfа не static, це не проблема.
Гонки легкості на орбіті

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. Це зовсім непотрібно.
Щеня

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 Спробуйте з клангом, і він вийде з ладу. Те, що це прийнято GCC, є помилкою, наскільки я знаю.

4
FWIW, struct S { int i; typedef decltype(i) Int; };працює, хоча iє нестатичним членом даних. Він працює, тому що decltypeмає особливий виняток, коли просте ім'я не оцінюється як вираз. Але я не можу придумати жодного способу використання цієї можливості таким чином, щоб відповісти на питання.

21

Що працює і в GCC, і в clang, це створити typedef, на який посилається this, використовуючи thisфункцію trade-return-type функції 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();
};

На жаль, суворе читання стандарту говорить про те, що навіть це не вірно. Що робить кланг - це перевірити, 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-статичний] / 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 спеціально модифікована також може бути застосована до статичних функцій - членів, і каже , що тип thisне є 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варіантами підтримки класу.
Якк - Адам Невраумон

Я думав саме про це, поки вчора залишав роботу. Ти мене до цього побив :). Я б запропонував оголосити self_check () як вбудований, щоб уникнути посилання на проблеми (той самий символ Foo :: self_check (), знайдений у кількох файлах об'єктів).
свиня

1
@theswine: 9,3 / 2 є індекс пункту в стандарті C ++, який гарантує , що функції - члени класу , певні в тілі визначення класу вже, неявно inline. Це означає, що вам взагалі не потрібно писати inline. Отже, якщо ви писали inlineперед кожним таким визначенням функції класу протягом усієї своєї кар'єри, ви можете зупинитися зараз;)
Гонки легкості в орбіті

2
@LightnessRacesinOrbit О, насправді я був. Дякую, що позбавить мене від друку в майбутньому :). Мене завжди дивує те, наскільки я не знаю про C ++.
свиня

11

Я також думаю, що це неможливо, ось ще одна невдала, але 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, "" );
}

що не вдається, оскільки 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є первинним значенням типу "покажчик на 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Однак у вас є дратівлива марна змінна
ММ

@Matt Правда, але мені все-таки було цікаво.

1
Це могло б працювати без = this, правда? А чому б не простоusing self = Foo*;
user362515

1
Ми нічого не отримати тут , звичайно, тому що ми повинні були оголосити test, що типу, гм, Foo *!
Пол Сандерс

4

Якщо тип не повинен бути тип члена вміщає класу можна замінити використання selfз decltype(*this). Якщо ви використовуєте його у багатьох місцях свого коду, ви можете визначити макрос SELFтаким чином:

#define SELF decltype(*this)

2
І ви не можете використовувати це поза класом або вкладених класах
Drax

1
@Drax: Це не повинно бути доступним поза класом.
Ben Voigt

@BenVoigt Але він може бути доступним у вкладених класах, що є IMO - найцікавішим випадком використання.
Дракс

1
Я не думаю, що так. Чи не слід selfпосилатися на клас, що безпосередньо прикладається, а не на зовнішній клас? Але я не знаю php так добре.
Бен Войгт

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

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, щоб надати повну відповідь. Я це кажу у відповіді. Це недійсне? Ви можете вказати як? Це складено чудово, я зараз його використовую.
свиня

1
Ідентифікатори є недійсними, оскільки C ++ залишає за компілятором усе, що підсумовує підкреслення, за яким йде велика літера, а також дві провідні підкреслення в глобальному масштабі. Користувацький код не повинен використовувати його, але не всі компілятори позначать його як помилку.
Конрад Рудольф

@KonradRudolph Я бачу, я цього не знав. У мене дуже багато коду, який використовує це, ніколи не було проблем з ним ні в Linux, ні в Mac / Windows. Але я гадаю, що це добре знати.
свиня

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 Як ​​це? Я цього не бачу. Я, поміркувавши, видалив остаточне речення моєї первісної публікації. Що я там мав спочатку, можливо, спонукало вас до цього.
Пол Сандерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.