为什么我的可变参数模板参数验证器拒绝在编译时求值?

Why does my variadic template argument verifier refuse to evaluate at compile time?

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

 #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;
 }

...到目前为止,还不错,以上所有对我来说都很好。

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

 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 函数在编译时被评估?有人可以提示我需要做哪些不同的事情才能让它发挥作用吗?

完美转发无罪

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

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

所以 static_assert() 测试可能只知道 运行 时间的值是错误的。

此外,在您的特定情况下

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

你没有把b定义成constexpr 所以编译器,即使可以选择在编译时执行它,通常也是在运行-time.

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

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

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

 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() 放在 class 的主体中,不一定要放在构造函数的主体中。

2) 在 std::integer_sequence 中将 vals... 作为模板参数传递(仅从 C++14 开始可用,但在 C+ 中用自定义 class 替换它很简单+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>{});