【问题标题】:Can I implement an autonomous `self` member type in C++?我可以在 C++ 中实现自主的“self”成员类型吗?
【发布时间】:2014-02-04 07:12:46
【问题描述】:

C++ lacks 等效于 PHP's self keyword,计算结果为封闭类的类型。

在每个班级的基础上伪造它很容易:

struct Foo
{
   typedef Foo self;
};

但我不得不再次写Foo。也许有一天我会弄错这个并导致一个无声的错误。

我可以使用decltype 和朋友的某种组合来“自主”完成这项工作吗? I tried the following alreadythis 在那个地方无效:

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

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

(我不会担心 static 的等效项,它的作用相同,但绑定较晚。)

【问题讨论】:

  • this_t 可能更符合常规 C++ 命名。
  • @BartekBanachewicz: 或 this_type
  • @Praetorian,我不记得这是否是一个提案,但有人建议 auto()~auto() 用于 ctors/dtors。至少可以说很有趣。如果用于此目的,可能是typedef auto self;,但这对我来说似乎有点粗略。
  • 老实说,如果我要建议使这成为可能的语法,它可能是decltype(class),也许与decltype(struct) 等效。这比在特定上下文中的 auto 要清楚得多,而且我认为它与基于 decltype(auto) 的语言相匹配没有任何问题。
  • 既然你想避免错误,你可以用 static_assert 设置一个虚拟成员函数,比如 void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); } 不适用于类模板...

标签: c++ c++11


【解决方案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 上运行。

【讨论】:

  • 我猜答案是“不,但拉尔夫可以!” ;)
  • 这比简单地把 typedef 放在那里有什么好处?天哪,你为什么还需要 typedef?为什么?
  • @MilesRout 这是一个关于问题的问题,而不是答案。在软件开发(尤其是维护)的许多情况下,避免代码中的冗余是有帮助的,因此在一个地方更改某些内容并不需要您在另一个地方更改代码。这就是 autodecltype 的全部意义,或者在这种情况下是 self
  • template&lt;typename T&gt;class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {}; 会更简单,并且可以更精确地控制继承 - 有什么反对的理由吗?
  • @mmmmmmmm,如果您还没有学会深入理解“不要重复自己”的原则,那么您可能还没有足够/认真地编码。这种“混乱”(实际上远非如此)在谈论不优雅的语言特征(或错误特征,甚至某些严格措施的缺陷)的上下文中是一个非常优雅的解决方案。
【解决方案2】:

一种可能的解决方法(因为您仍然必须编写一次类型):

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

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

为了更安全的版本,我们可以确保T 实际上派生自Self&lt;T&gt;

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

请注意,成员函数中的static_assert 可能是唯一的检查方法,因为传递给std::is_base_of 的类型必须是完整的。

【讨论】:

  • typedef 中不需要typename。而且由于这不会减少裁员的数量,我认为这不是一个可行的选择。
  • Foo名字重复的问题一模一样。
  • 略优于原来的方法,因为重复非常接近。不是问题的解决方案,而是 +1 值得尝试最佳解决方法。
  • 我多次使用该解决方案,但它有一个不好的地方:当后来从Foo 派生时,您必须:(1)将 T 向上传播到叶子后代,或 (2) 记得多次从 SelfT 继承,或 (3) 接受所有子对象都是 Base.. 可用,但不漂亮。
  • @quetzalcoatl:因为我试图复制 self 而不是 static,所以没问题。
【解决方案3】:

您可以使用宏来代替常规的类声明,这将为您做到这一点。

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

然后用like

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; 可能有助于提高可读性。


您也可以使用 @Paranaix 的 Self 并使用它(它开始变得非常老套)

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

class WITH_SELF(Foo) {
};

【讨论】:

  • EWWWW END_CLASS。这是完全没有必要的。
  • @DeadMG 我想有些人可能更喜欢一致性;毕竟,第一次使用宏不会以{ 结尾,所以} 是“挂起”的,文本编辑器也可能不喜欢。
  • 好主意,但即使我从根本上不反对宏,我也只会在它模仿 C++ 作用域时接受它的使用,即如果它可以用作 CLASS_WITH_SELF(foo) { … }; - 我认为这是不可能的达到。
  • @KonradRudolph 我也添加了一种方法来做到这一点。不是我喜欢它,只是为了完整
  • 这种方法存在一些问题。第一个不允许您轻松地使类继承(除非您使用另一个宏参数),第二个是Self 所具有的继承它的所有问题。跨度>
【解决方案4】:

我没有确凿的证据,但我认为这是不可能的。以下失败 - 原因与您的尝试相同 - 我认为这是我们能得到的最远距离:

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

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

本质上,这表明我们想要声明 typedef 的范围只是没有访问(直接或间接)this,并且没有其他(独立于编译器) 获取类的类型或名称的方式。

【讨论】:

  • 这可能是C++1y的返回类型推导吗?
  • @dyp 就我的回答而言,这不会改变任何事情。这里的错误不在尾随返回类型中,而是在调用中。
  • @quetzalcoatl:decltype 的内部结构是一个未评估的上下文,因此调用成员函数不是问题(不会尝试)
  • @TomKnapen 用 clang 试试,它会失败。据我所知,它被 GCC 接受的事实是一个错误。
  • FWIW, struct S { int i; typedef decltype(i) Int; }; 即使i 是一个非静态数据成员也可以工作。它之所以有效,是因为decltype 有一个特殊异常,即简单名称不会被评估为表达式。但我想不出任何方式来使用这种可能性来回答问题。
【解决方案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 是否用于尾随返回类型,无论函数类型如何,它甚至允许 static 成员函数。但是,标准实际要求的是,this 不能在非静态成员函数(或非静态数据成员初始化程序)的定义之外使用。英特尔做对了并拒绝了这一点。

鉴于:

  • this 仅允许在非静态数据成员初始化器和非静态成员函数 ([expr.prim.general]p5) 中使用,
  • 非静态数据成员的类型不能从初始化程序 ([dcl.spec.auto]p5) 推导出来,
  • 非静态成员函数只能通过函数调用上下文中的非限定名称引用 ([expr.ref]p4)
  • this可以使用时([over.call.func]p3),非静态成员函数只能通过非限定名称调用,即使在未求值的上下文中,
  • 通过限定名称或成员访问对非静态成员函数的引用需要对所定义类型的引用

我想我可以肯定地说,如果不以某种方式在某处包含类型名称,就根本无法实现 self

编辑:我之前的推理存在缺陷。 “非静态成员函数只能通过非限定名称调用,即使在未评估的上下文中,当 this 可以使用时([over.call.func]p3)”是不正确的。它实际上说的是

如果关键字this (9.3.2) 在范围内并引用类TT 的派生类,则隐含对象参数为(*this)。如果关键字this 不在范围内或引用另一个类,则T 类型的人为对象将成为隐含的​​对象参数。如果参数列表由一个人为的对象扩充,并且重载决议选择了T 的非静态成员函数之一,则该调用是格式错误的。

在静态成员函数内部,this 可能不会出现,但它仍然存在。

但是,根据 cmets,在静态成员函数内部,不会执行 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 它说“在可以使用this 的上下文中用于X 类的成员中”,所以我认为不会执行转换。
  • 但是它既不是隐式也不是显式的类成员访问..? [expr.call]/1 "对于成员函数调用,后缀表达式应为隐式或显式类成员访问[...]"
  • (我的意思是,当你有auto _self_fn_1() -&gt; decltype(*this); auto _self_fn_1() const -&gt; decltype(*this);时会发生什么?)
  • @dyp [expr.call]/1 是个好点,我得仔细看看。不过,关于const 重载:这不是问题。 5.1p3专门修改为也适用于静态成员函数,并说this的类型是Foo*/Bar*(没有const),因为@987654354的声明中没有const @.
【解决方案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

我们可以做一些小技巧让它也适用于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

在您的类中创建一个大小为 1 字节的空 struct。如果您的类型已实例化,则会针对 self 进行测试。

【讨论】:

  • 也不错!
  • @LightnessRacesinOrbit 现在带有template 类支持选项。
  • 我昨天下班时正在考虑这个问题。你打败了我:)。我建议将 self_check() 声明为内联,以避免链接问题(在多个目标文件中发现相同的符号 Foo::self_check())。
  • @theswine: 9.3/2 是 C++ 标准中段落的索引,它保证类成员函数在类定义的主体中定义已经隐含,inline。这意味着您根本不需要写inline。因此,如果您在整个职业生涯中一直在每个此类成员函数定义前面写inline,那么您现在可以停下来;)
  • @LightnessRacesinOrbit 哦,其实我是。谢谢,这将为我节省一些将来的打字时间:)。我总是惊讶于我对 C++ 的了解之多。
【解决方案7】:

我也认为这是不可能的,这是另一个失败但恕我直言的有趣尝试,它避免了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(&amp;self_var) self_ptr 也不起作用,那只是一个普通的int*
【解决方案8】:

我最近发现 brace-or-equal-initializer 中允许使用 *this。 § 5.1.1 中描述(来自n3337 working draft):

3 [..] 与其他上下文中的对象表达式不同,*this 是 就集体成员而言,不需要是完整的类型 在成员函数体之外访问(5.2.5)。 [..]

4 否则,如果 member-declarator 声明了一个非静态数据 类 X 的成员 (9.2),表达式 this 是类型的纯右值 可选的 brace-or-equal-initializer 中的“指向 X 的指针”。它 不应出现在 member-declarator 的其他位置。

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

通过Daniel Frey'sstatic_assert

Live example

【讨论】:

  • 虽然你有一个烦人的无用变量test
  • @Matt True,但我仍然觉得它很有趣。
  • 如果没有= this,这可能会奏效,对吧?为什么不只是using self = Foo*;
  • 我们在这里肯定没有任何收获,因为我们必须将test 声明为类型,嗯,Foo *
【解决方案9】:

除非类型需要是封闭类的成员类型,否则您可以将self 的使用替换为decltype(*this)。如果您在代码中的许多地方使用它,您可以定义一个宏SELF,如下所示:

#define SELF decltype(*this)

【讨论】:

  • 而且你不能在课堂外或嵌套类中使用它
  • @Drax:它不应该在课堂外可用。
  • @BenVoigt 但它应该在嵌套类中可用,这是 IMO 最有趣的用例。
  • 我不这么认为。 self 不应该指代直接封闭的类而不是外部类吗?但是我对php不太了解。
  • @LightnessRacesinOrbit:我猜代码和错误应该是“PHP 没有嵌套类型”?
【解决方案10】:

基于 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";
}

【讨论】:

【解决方案11】:

提供我的版本。最好的一点是它的用法和原生类一样。但是,它不适用于模板类。

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

【讨论】:

    【解决方案12】:

    终于找到了合适的解决方案!这个想法来自@MitalAshok on github

    #include <type_traits>
    
    namespace SelfType
    {
        template <typename T>
        struct Reader
        {
            friend auto adl_GetSelfType(Reader<T>);
        };
    
        template <typename T, typename U>
        struct Writer
        {
            friend auto adl_GetSelfType(Reader<T>){return U{};}
        };
    
        inline void adl_GetSelfType() {}
    
        template <typename T>
        using Read = std::remove_pointer_t<decltype(adl_GetSelfType(Reader<T>{}))>;
    }
    
    #define DEFINE_SELF \
        struct _self_type_tag {}; \
        constexpr auto _self_type_helper() -> decltype(::SelfType::Writer<_self_type_tag, decltype(this)>{}, void()) {} \
        using Self = ::SelfType::Read<_self_type_tag>;
    

    然后:

    struct A
    {
        DEFINE_SELF
        static_assert(std::is_same_v<Self, A>);
    };
    

    这使用有状态模板元编程将类型存储在可访问的上下文中(在辅助函数的尾随返回类型中),然后读取否则无法访问的类型(在类范围内)。

    这里的关键是编写器是如何实例化的。简单地执行auto _self_type_helper() -&gt; Writer&lt;...&gt; {return {};} 是行不通的:编写器被延迟实例化,使得状态可以在任何成员函数体内访问,但不能在类范围内访问。

    但是,如果您使用-&gt; decltype(Writer&lt;...&gt;{}, void()),或者以其他方式使编写器成为影响类型的表达式的一部分,那么它就会开始工作。我不完全确定为什么会这样。

    【讨论】:

    【解决方案13】:

    我将重复“必须自己做”的明显解决方案。这是代码的简洁 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
    

    有点长,但请耐心等待。这具有在没有decltype 的情况下在C++03 中工作的优势,因为__self_check_helper 函数用于推断this 的类型。此外,没有static_assert,而是采用了sizeof() 技巧。对于 C++0x,你可以让它更短。现在这不适用于模板。此外,宏的末尾不期望分号还有一个小问题,如果使用 pedantic 编译,它会抱怨额外的不必要的分号(或者您将留下一个看起来很奇怪的宏,在 @ 的正文中不以分号结尾987654331@ 和 ABC)。

    检查传递给DECLARE_SELFType 不是一种选择,因为这只会检查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 测试。事实上,对于 XYZABC 示例,它会抱怨:

    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 位仍然是必需的,因为它会生成一个类型,该类型在模板主体中是 typedef-ed(我想 static_assert 不能这样做)。这种方法的优势仍然是它的零成本。

    【讨论】:

    • 您实际上是在此处重新实现static_assert,不是吗?此外,您的完整代码无效,因为您使用了非法(保留)标识符。
    • @KonradRudolph 是的,确实如此。我当时没有 C++0x,所以我重新实现了 static_assert 以提供完整的答案。我在答案中这么说。无效吗?你能指出怎么做吗?它编译得很好,我现在正在使用它。
    • 标识符无效,因为 C++ 为编译器保留前导下划线后跟大写字母以及两个前导下划线的所有内容。用户代码不得使用它,但并非所有编译器都会将其标记为错误。
    • @KonradRudolph 我明白了,我不知道。我有很多使用它的代码,在 Linux / Mac / Windows 上都没有遇到过问题。但我想知道是件好事。
    【解决方案14】:

    我对这些古怪的模板一无所知,超级简单的东西怎么样:

    #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 来声明你的构造函数(当然还有析构函数)。

    Live demo.

    【讨论】:

    • 它对如何/必须如何使用该类有相当显着的影响
    • @LightnessRacesinOrbit 怎么样?我没看到。经过反思,我确实删除了原始帖子的最后一句话。我原本在那里的东西可能会让你这么想。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    • 2014-04-10
    • 2022-06-15
    • 2012-02-25
    相关资源
    最近更新 更多