【问题标题】:Ensure derived class implements static method确保派生类实现静态方法
【发布时间】:2014-04-29 14:14:37
【问题描述】:

我想确保派生类实现特定的静态方法。我认为这样做应该可以使用static_assertstd::is_samedecltypeCRTP,也许还可以使用SFINAE。但是,到目前为止,我发现 similar code 相当复杂,而且我似乎还没有完全理解它,这使我无法根据自己的需要采用它。

到目前为止我尝试的是这个

template <class T>
class Base 
{
    static_assert(std::is_same<decltype(T::foo(1)), int>::value, "ERROR STRING");
};

class Derived : public Base <Derived>
{
public:
    static int foo(int i) { return 42; };
};

但是,它不会编译告诉我,即使该方法正确实现,Derived 也没有名为 foo 的元素。此外,在 static_assert 内部的表达式中为 foo 提供实际参数感觉不对。

搜索 SO 揭示了一个类似的问题,最终将我引向 this piece of code,在那里检查了一个类型是否有方法 begin() 和 end() 返回迭代器。因此,我尝试根据需要采用此代码。

template <class T>
class Base 
{
    template<typename C>
    static char(&g(typename std::enable_if<std::is_same<decltype(static_cast<int(C::*)(int)>(&C::foo)), int(C::*)(int)>::value, void>::type*))[1];

    template<typename C>
    static char(&g(...))[2];

    static_assert(sizeof(g<T>(0)) == 1, "ERROR STRING");
};

但由于断言触发,此代码无法编译。

所以我的问题是

  1. 为什么编译器在我的第一个示例中找不到 Derived::foo?
  2. 示例代码中的typename C::const_iterator(C::*)() const 究竟是什么意思?它不是一个返回 C::const_iterator 并且不带参数的 const 函数吗? C::* 到底是什么意思?那么为什么int(C::*)(int) 在我的情况下是错误的呢?
  3. 如何正确解决我的问题?

我正在使用 MSVC 12,但如果可能,代码应该是可移植的。

【问题讨论】:

  • 你不是解析器,不要标记你的代码! :) 模板已经很难阅读了;将一行分成太多行只会让事情变得更糟。
  • "ERROR STRING" 是断言失败的错误消息选择。正确的选择是"Jabberwocky is killing user."
  • 因为 Base 和 Derived 是错误的类名,而 foo 是错误的方法名。 ;) 但这意味着 SSCCE,我认为字符串并不重要。但是,“ERROR STRING”是为了说明存在一些真正的错误字符串。我选择了“T需要实现一个带有签名的方法......”之类的东西。但无论如何感谢您的评论。 :)

标签: c++ c++11 typetraits crtp static-assert


【解决方案1】:

这是使用 CRTP 时的常见问题:Base&lt;Derived&gt;Derived 的碱基列表中遇到它时被实例化,此时Derived 还不是一个完整的类型,因为其余的它的声明还没有被解析。有各种解决方法。对于static_assert,您需要延迟断言的实例化,直到Derived 完成。一种方法是将断言放入您知道必须实例化的 Base 的成员函数中 - 析构函数始终是一个不错的选择 (Live at Coliru):

template <class T>
class Base 
{
public:
    ~Base() {
        static_assert(std::is_same<decltype(T::foo(1)), int>::value, "ERROR STRING");
    }
};

class Derived : public Base<Derived>
{
public:
    static int foo(int) { return 42; };
};

解决问题 #2:C::* 是“指向类 C 成员的指针”的语法。所以int(*)(int) 是“指向函数的指针,采用单个int 参数并返回int”,int(C::*)(int) 类似地“指向C 的成员函数的指针,采用单个int 参数并返回int 。”怪物

typename C::const_iterator(C::*)() const

将转换为“指向C 的常量成员函数的指针,不带参数并返回C::const_iterator”当然typename 是必需的,以表明依赖名称C::const_iterator 是一种类型。

【讨论】:

  • 当然!为什么我一次又一次地遇到同样的错误?感谢您(再次)向我指出这一点;)但是,为 foo() 提供实际参数仍然感觉很奇怪:/
  • @sigy 如果它真的困扰你,你可以使用std::declval 来创建一个“通用”整数右值表达式:static_assert(std::is_same&lt;decltype(T::foo(std::declval&lt;int&gt;())), int&gt;::value, "ERROR STRING");。您也可以考虑使用is_convertible&lt;decltype(...), int&gt; 而不是is_same
  • int(C::*)(int) 是错误的,因为&amp;Derived::foo 的类型是int(*)(int),因为它是static 成员函数。如果你需要一个左值,你可以做std::declval&lt;int&amp;&gt;()
  • 这个答案有一个小问题:写出析构函数会导致禁用移动构造函数和移动赋值运算符的生成。它还可以防止对象成为trivially_destructible。因此,虽然它有效,但这种方法也有缺点(这些缺点也适用于其他特殊成员)。
  • @sigy 我对std::declval&lt;Foo&amp;&gt;() 没有问题,其中Foo 是一个类类型。我不知道为什么add_rvalue_reference 会导致任何问题。也许您应该为此创建一个新问题。
猜你喜欢
  • 2013-09-02
  • 1970-01-01
  • 1970-01-01
  • 2012-05-24
  • 1970-01-01
  • 2011-02-10
  • 2014-04-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多