static_assert在构造函数的初始化列表之前

时间:2017-11-26 13:12:18

标签: c++ static-assert

有一个非模板化的类,它有一个模板化的构造函数。是否可以在这样的构造函数中初始化成员变量之前检查静态断言?​​

例如,在检查T::value()是否有此类方法之前,以下代码会执行T

class MyClass
{
public:
    template<typename T>
    MyClass(const T &t)
        : m_value(t.value())
    {
        static_assert(HasValueMethod<T>::value, "T must have a value() method");
    }

private:
    int m_value;
};

static_assert放置在构造函数的正文中工作正常,除非它打印&#34; T必须有一个value()方法&#34;最后,在成员初始化列表的所有错误消息之后,例如:

prog.cpp: In instantiation of ‘MyClass::MyClass(const T&) [with T = int]’:
prog.cpp:24:16:   required from here
prog.cpp:12:21: error: request for member ‘value’ in ‘t’, which is of non-class type ‘const int’
         : m_value(t.value())
                   ~~^~~~~
prog.cpp:14:9: error: static assertion failed: T must have a value() method
         static_assert(HasValueMethod<T>::value, "T must have a value() method");
         ^~~~~~~~~~~~~

我发现这有点令人困惑,并想知道是否可以打印&#34; T必须有一个value()方法&#34;在尝试初始化成员变量之前。

我知道我可以使用enable_if和SFINAE为不适当的T禁用此构造函数,但我想告诉用户一些比#&#34;找不到的方法更有意义的东西&#34 ;

3 个答案:

答案 0 :(得分:4)

根据std::enable_if是否具有函数成员static_assert,您可以使用T向SFINAE输出执行value()的构造函数,从而保持实际实现的分离。

如果T具有value()方法,则会选择第一个构造函数,并且正常实现(除了需要std::enable_if才能选择):

template <typename T, typename = std::enable_if_t<HasValueMethod<T>::value>>
MyClass(const T &t) : m_value(t.value())
{}

所以我们需要第二个构造函数是SFINAEd的函数重载,因为第一个构造函数已经知道T::value存在:

template <typename T, typename = std::enable_if_t<!HasValueMethod<T>::value>>
MyClass(const T &, ...)
{
  static_assert(HasValueMethod<T>::value, "T must have a value() method");
}

注意可变参数...:为了区分构造函数的原型需要它,因此它不会与第一个碰撞(它们需要不同,否则模糊的原型会导致编译错误)。你不会传递任何东西,它只是让它成为一个不同的原型。

请注意std::enable_if的谓词是相同的但是否定了。当HasValueMethod<T>::value为false时,第一个构造函数是SFINAEd,不是函数重载,而是第二个构造函数,它会触发静态断言。

您仍然需要在静态断言的参数中使用HasValueMethod<T>::value,因此它依赖于T来执行。否则,仅将false置于其中将使其始终触发,无论是否被选中。

以下是T没有.value()时GCC打印的内容:

main.cpp: In instantiation of 'MyClass::MyClass(const T&, ...) [with T = A; <template-parameter-1-2> = void]':
main.cpp:35:18:   required from here
main.cpp:21:9: error: static assertion failed: T must have a value() method
         static_assert(HasValueMethod<T>::value, "T must have a value() method");

         ^~~~~~~~~~~~~

这是Clang的:

main.cpp:21:9: error: static_assert failed "T must have a value() method"
        static_assert(HasValueMethod<T>::value, "T must have a value() method");
        ^    

总而言之,这个方法存在一个问题(正如@ T.C。在评论中指出的那样):MyClass现在可以从未评估的上下文的角度来看。也就是说,

static_assert(std::is_convertible_v</*anything*/, MyClass>); // Always true.

在C ++ 20中,当希望概念出现时,可以使用requires子句轻松解决这个问题:

template <typename T>
  requires HasValueMethod<T>::value
MyClass(const T &t) : m_value(t.value())
{}

你也可以直接在HasValueMethod<T>条款中表达requires

template <typename T>
  requires requires (T a) { { a.value() } -> int; }
MyClass(const T &t) : m_value(t.value())
{}

或将HasValueMethod<T>转化为真实的概念:

template <typename T>
concept HasValueMethod = requires (T a) {
    { a.value() } -> int;
};

// Inside `class MyClass`.
template <typename T>
  requires HasValueMethod<T>
MyClass(const T &t) : m_value(t.value())
{}

此类解决方案也使std::is_convertible_v<T, MyClass>按预期工作。

答案 1 :(得分:2)

使static_assert()更接近使用情况。在这种情况下,辅助函数将执行此操作:

class MyClass
{
    template<typename T>
    static int get_value(const T& t)
    {
        static_assert(HasValueMethod<T>::value, "T must have a value() method");
        return t.value();
    }

public:
    template<typename T>
    MyClass(const T &t)
        : m_value(get_value(t))
    {
    }

private:
    int m_value;
};

这不仅可以修复错误消息的顺序,还可以让您为需要value()成员函数的每个路径重用该消息。

答案 2 :(得分:1)

如果你不打算SFINAE约束构造函数并且总是希望在HasValueMethod为false时引发错误,那么你可以写一个'hard'变体你的特质班:

template<class T>
struct AssertValueMethod
{
  static_assert(HasValueMethod<T>::value, "T must have a value() method");
  using type = void; // note: needed to ensure instantiation, see below ...
};

template< typename T, typename = typename AssertValueMethod<T>::type >
MyClass(const T &t): ...

此外,如果稍后要添加一个sfinae选择的重载,您总是可以编写一个正确的委托构造函数而无需更改静态断言逻辑...