如何使用static_assert检查派生类是否覆盖非虚基类方法?

时间:2012-07-15 14:51:29

标签: c++ templates metaprogramming

一个例子:

template <typename TDerived>
struct Base
{
    void DoSomething()
    {
        if (static_cast<TDerived*>(this)->CheckSomeFlag())
        {
            std::cout << "true" << std::endl;
        }
        else
        {
            std::cout << "false" << std::endl;
        }
    }

    // Default implementation
    bool CheckSomeFlag()
    {
        return false;
    }
};

struct Derived : public Base<Derived>
{
    bool CheckSomeflag()
    {
        return true;
    }
};

int main(int argc, char** argv)
{
    Derived d;

    d.DoSomething();
    return 0;
}

在这种情况下,程序打印“false”,因为程序员在Derived :: CheckSomeFlag()的声明中有一个拼写错误,而F是小写的。我想在编译时使用类似OVERRIDE(x)的宏来捕获这样的情况,它扩展为static_assert(is_member_of<Base, x>::value, "error");但不确定完成此操作所需的模板技巧。

4 个答案:

答案 0 :(得分:2)

模板和宏的混合可以提供:

  • 如果未在基类上声明类似的函数,则静态断言。
  • 在派生类中声明函数。

使用这种方法,宏将定义一个类型特征模板,用于测试基类上是否存在特定成员函数。当基类具有指定的成员函数时,特征的value成员将为true。然后在静态断言中使用类型特征的value

#define TRAITNAME_HELPER( PREFIX, FN, LN )  PREFIX ## FN ## LN
#define TRAITNAME( FN, LN ) TRAITNAME_HELPER( has_fn, FN, LN )

#define OVERRIDE_IMPL( CLASS, RETURN_TYPE, FN, ARG_TYPES, LN )  \
  /* Type trait used to determine if                            \
   * RETURN_TYPE T::FN( ARG_TYPES ) exists. */                  \
  template < typename T >                                       \
  struct TRAITNAME( FN, LN )                                    \
  {                                                             \
    /* Type that expects a value for the specific type.  For    \
     * example, type_check< int, 4 >. */                        \
    template < typename U, U > struct type_check {};            \
                                                                \
    /* Use type_check expect a specific                         \
     * pointer-to-member-function on T. */                      \
    template < typename U >                                     \
    static std::true_type                                       \
    check( type_check< RETURN_TYPE (T::*)ARG_TYPES,             \
                       &U::FN >* = 0 );                         \
                                                                \
    template < typename U >                                     \
    static std::false_type check( ... );                        \
                                                                \
    /* Determine which check function was resolved for T. */    \
    typedef decltype( check< T >( 0 ) ) type;                   \
    static constexpr decltype(type::value) value = type::value; \
  };                                                            \
  static_assert( TRAITNAME( FN, LN )< CLASS >::value,           \
                 "" #RETURN_TYPE " " #FN #ARG_TYPES             \
                 " is not defined in " #CLASS "." );            \
  RETURN_TYPE FN ARG_TYPES
#define OVERRIDE( CLASS, RETURN_TYPE, FN, ARG_TYPES )           \
  OVERRIDE_IMPL( CLASS, RETURN_TYPE, FN, ARG_TYPES, __LINE__ )
  • OVERRIDE_IMPL宏末尾没有分号,允许在类中声明或定义成员函数。
  • 需要额外级别的宏来支持重载方法。它使用__LINE__创建一个独特的类型特征。

使用以下Base类:

template < typename TDerived >
struct Base
{
  bool CheckSomeFlag();
  bool CheckSomeFlag(int, int);
};

如果Dervied类定义为:

struct Derived : public Base< Derived >
{
  OVERRIDE( Base< Derived >, bool, CheckSomeflag, () );
};

然后编译失败并出现以下错误(demo):

error: static assertion failed: "bool CheckSomeflag() is not defined in Base< Derived >."

但是,当类型正确时,它将进行编译,如所示here

struct Derived : public Base< Derived >
{
  OVERRIDE( Base< Derived >, bool, CheckSomeFlag, () );
  OVERRIDE( Base< Derived >, bool, CheckSomeFlag, (int a, int b) )
  {
    return ( a > b );
  }
};
bool Derived::CheckSomeFlag() { return true; }

这种方法很快就会出现两个缺点:

  • 返回类型必须完全匹配。如果没有一些额外的工作,这可以防止使用协变返回类型。但是,这可能是CRTP模式所需的行为。
  • 宏语法模糊了函数类型。例如,不是能够使用bool(int,int)作为返回bool且具有两个int参数的函数的类型,而是必须将其作为两个单独的参数传递给宏{ {1}}。这可以通过改变宏期望的参数顺序来缓解,但我选择顺序来匹配函数的正常声明:( ..., bool, ..., (int, int) )

答案 1 :(得分:1)

这是C ++ 11中的一个想法:

#include <iostream>
#include <type_traits>

struct Foo { void f() { } };
struct Bar : Foo { void f() { } };
struct Zip : Foo {              };

int main()
{
    std::cout << "Bar: " << std::is_same<decltype(&Bar::f), void(Foo::*)()>::value << std::endl
              << "Zip: " << std::is_same<decltype(&Zip::f), void(Foo::*)()>::value << std::endl
    ;
}

这可能会变成静态断言。

答案 2 :(得分:0)

你不能从代码中做到这一点。我建议您查看一些静态分析工具,您可以使用这些工具来捕获常见的(并非常见的)程序员错误。 它们很多。一个例子:http://www.coverity.com/products/static-analysis.html

这是一个常见的程序员错误,基类作者应该知道这一点。 请注意,如果要提供默认实现但希望强制派生类程序员覆盖该方法以避免错误,则可以使用纯虚拟声明来使用以下技巧:

class Base {
  public:
  virtual void foo() = 0;
};
// Did you know that you can still implement pure virtual functions? Neat!
void Base::foo() {
  // does something useful that might be applicable to most cases
}
class Derived : public Base {
  typedef Base superclass;
  public:
  void foo() {
    // I am forced to override foo
    // But author of Base kindly provided a default implementation
    superclass::foo();
  }
};

答案 3 :(得分:0)

这似乎给你一个编译时错误:

...
template <bool (TDerived::*TPointer)()> class Checker { };

void DoSomething()
{
    static Checker<&TDerived::CheckSomeFlag> foo;
...

我的想法是(我不是模板专家)我只是试图访问TDerived中的CheckSomeFlag成员,并且编译器将无法用指向Base成员的指针替换它继承只能以其他方式工作(或者根本不工作,我对非虚方法不太确定)