确保派生类实现静态方法

sig*_*igy 7 c++ static-assert crtp type-traits c++11

我想确保派生类实现一个特定的静态方法.我认为这样做应该可以使用static_assert,std :: is_same,decltype,CRTP并且可能使用SFINAE.但是,到目前为止,我发现的类似代码非常复杂,似乎我还没有完全理解它使我无法将其用于我的需求.

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

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; };
};
Run Code Online (Sandbox Code Playgroud)

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

搜索SO揭示了一个类似的问题,最终引导我到这段代码,检查一个类型有方法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");
};
Run Code Online (Sandbox Code Playgroud)

但是这段代码没有编译,因为断言触发了.

所以我的问题是

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

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

Cas*_*sey 12

这是使用CRTP时的一个常见问题:Base<Derived>Derived基数列表中遇到它时被实例化,此时它还Derived不是一个完整的类型,因为其声明的其余部分尚未被解析.有各种解决方法.因为static_assert,您需要延迟断言的实例化,直到Derived完成.一种方法是将断言放在一个Base你知道必须实例化的成员函数中- 析构函数总是一个很好的选择(在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; };
};
Run Code Online (Sandbox Code Playgroud)

解决问题#2:C::*是"指向类成员的指针"的语法C.所以int(*)(int)是"函数指针服用单一int参数并返回int",并且int(C::*)(int)是类似于"指针的成员函数C采用单个int参数并返回int".怪物

typename C::const_iterator(C::*)() const
Run Code Online (Sandbox Code Playgroud)

将转换为"指向C不带参数并返回的常量成员函数的指针C::const_iterator",当然typename必须指示依赖名称C::const_iterator是一个类型.

  • `int(C ::*)(int)`是错误的,因为`&Derived :: foo`的类型是`int(*)(int)`因为它是`static`成员函数.如果你需要一个左值,你可以做`std :: declval <int&>()`. (3认同)
  • 这个答案有一个小问题:写出析构函数会导致禁止生成移动构造函数和移动赋值运算符.它还可以防止对象[trivially_destructible](http://en.cppreference.com/w/cpp/types/is_destructible).因此,虽然它有效,但该方法存在缺点(这种缺点适用于其他特殊成员). (3认同)