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

问题描述 投票:5回答:3

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

例如,以下代码在检查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放置在构造函数的正文中工作正常,除非在成员初始化列表的所有错误消息之后打印“T必须有一个value()方法”,例如:

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");
         ^~~~~~~~~~~~~

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

我知道我可以使用enable_if和SFINAE来禁用这个构造函数来处理不合适的Ts,但我想告诉用户一些比“找不到方法”更有意义的东西。

c++ static-assert
3个回答
4
投票

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

如果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,不是函数重载,而是第二个,然后会触发静态断言。

你仍然需要在static assert的参数中使用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>也按预期工作。


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()成员函数的每个路径重用该消息。


1
投票

如果你不打算SFINAE约束构造函数,并且你总是希望在HasValueMethod为false时引发错误,你可以写一个特性类的“硬”变体:

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选择的重载,你总是可以编写一个正确的委托构造函数而不需要改变静态断言逻辑......

© www.soinside.com 2019 - 2024. All rights reserved.