首页 > 解决方案 > 为什么我的可变参数模板参数验证器拒绝在编译时进行评估?

问题描述

我有一个递归可变参数模板函数,可用于在编译时评估其参数,以确保它们都不大于指定的最大值:

 #include <type_traits>

 // base-case
 template <int MaxVal, typename T>
 constexpr T returnZeroIffValuesAreNotTooBig(const T t) 
 {
    return (t>MaxVal)?1:0;
 }

 // recursive-case
 template <int MaxVal, typename T, typename... Rest> constexpr T returnZeroIffValuesAreNotTooBig(const T t, Rest&&... rest)
 {
    return returnZeroIffValuesAreNotTooBig<MaxVal>(t) 
         + returnZeroIffValuesAreNotTooBig<MaxVal>(std::forward<Rest>(rest)...);
 }

 int main(int argc, char ** argv)
 {
    static_assert(returnZeroIffValuesAreNotTooBig<6>(1,2,3)==0, "compiles (as expected)");
    static_assert(returnZeroIffValuesAreNotTooBig<6>(1,2,3,4,5,6,7)==0, "generates compile-time error (as expected, because one of the args is greater than 6)");
    return 0;
 }

...到目前为止,一切都很好,以上所有对我来说都很好。

现在我想在我的类的可变参数构造函数中使用该函数:

 template<int MaxVal> class MyClass
 {
 public:
    template<typename ...Vals> constexpr explicit MyClass(Vals&&... vals)
    {
       // verify at compile-time that all values in (vals) are <= MaxVal
       static_assert(returnZeroIffValuesAreNotTooBig<MaxVal>(std::forward<Vals>(vals)...)==0, "why doesn't this compile?");
    }
 };

 int main(int argc, char ** argv)
 {
    MyClass<5> b(1,2,3,4);  // should compile, but doesn't!?
    return 0;
 }

...这不会编译;相反,我总是收到此错误:

 $ g++ -std=c++11 ./test.cpp
 ./test.cpp:12:80: error: static_assert expression is not an integral constant
       expression
   ...returnZeroIffValuesAreNotTooBig<MaxVal>(std::forward<Vals>(vals)...)==0...
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
 ./test.cpp:21:15: note: in instantiation of function template specialization
       'MyClass<5>::MyClass<int, int, int, int>' requested here
    MyClass<5> b(1,2,3,4);
               ^
 1 error generated.

...显然我的完美转发是不完美的,这会阻止returnZeroIffValuesAreNotToBig函数在编译时被评估?有人可以给我一个提示,让我知道我需要做些什么才能让它发挥作用吗?

标签: c++11variadic-templatesconstexprperfect-forwardingstatic-assert

解决方案


完美转发是无罪的。

正如 Caramiriel 所指出的,您不能在static_assert()测试中使用函数参数。

我知道构造函数是constexpr,但是constexpr构造函数(像任何其他constexpr函数一样)可以根据情况在编译时或运行时执行。

因此static_assert(),具有可能仅在运行时已知的值的测试是错误的。

此外,在您的特定情况下

MyClass<5> b(1,2,3,4); 

你没有这么定义b编译constexpr器,即使可以选择在编译时执行它,通常也是在运行时执行它。

如果要执行 a static_assert(),则必须使这些值在编译时可用。我知道的方式是将值作为模板值传递。不幸的是,没有办法为构造函数显式模板值,所以我看到了两种可能的方法

1)传递vals...作为类模板参数。

如下所示(注意:代码未经测试)

 template<int MaxVal, int ... vals> class MyClass
 {
 public:
    constexpr MyClass ()
    {
       // verify at compile-time that all values in (vals) are <= MaxVal
       static_assert(returnZeroIffValuesAreNotTooBig<MaxVal>(vals...)==0,
                     "why doesn't this compile?");
    }
 };

MyClass<5, 1, 2, 3, 4>  b;

在这种情况下,您也可以将 放在static_assert()类的主体中,而不必放在构造函数的主体中。

2) 在构造函数参数中传递vals...as 模板参数std::integer_sequence(仅从 C++14 开始可用,但用 C++11 中的自定义类替换它很简单)。

如下所示(注意:代码未经测试)

 template<int MaxVal> class MyClass
 {
 public:
    template <int ... vals>
    constexpr MyClass (std::integer_sequence<int, vals...> const &)
    {
       // verify at compile-time that all values in (vals) are <= MaxVal
       static_assert(returnZeroIffValuesAreNotTooBig<MaxVal>(vals...)==0,
                     "why doesn't this compile?");
    }
 }; 

MyClass<5> b(std::integer_sequence<int, 1, 2, 3, 4>{});

推荐阅读