为什么我的可变参数模板参数验证器拒绝在编译时求值?
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>{});
我有一个递归可变参数模板函数,我可以用它在编译时评估它的参数,以确保其中 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>{});