您如何 static_assert 可变参数模板的参数包中的值?
How do you static_assert the values in a parameter pack of a variadic template?
我正在创建可变参数模板。
假设我有这样的东西:
template<typename T, T ... Numbers>
class Sequence final {
// Unpack parameter pack into a constexpr array
constexpr static T count = sizeof...(Numbers);
constexpr static T numbers[count] = { Numbers... };
// ...
}
这个 class 的实例可以像这样实例化:
Sequence<uint32_t, 1, 2, 3, 42, 25> seq;
我想在编译时使用 static_assert
确保 numbers
参数包仅包含特定数字。为了这个例子,假设我只想允许 0
或 1
.
所以我想做类似的事情:
for (size_t i = 0; i < count; i++) {
static_assert(numbers[i] == 1 || numbers[i] == 0, "Only ones and zeroes are allowed.");
}
但显然,static_assert
不适用于 for
循环。我很确定这一定有某种语法,但我还没弄明白。
我更愿意使用使用 C++11 编译器(或者 C++14 编译器,如果它在 C++11 中不可用)编译的东西。
您不能对编译时值使用传统的 for 循环,但是有许多方法可以迭代编译时集合。但是,在您的情况下,没有必要显式循环遍历每个数字:您可以使用包扩展来确保数字仅为 0
或 1
:
#include <type_traits>
// We define a `conjunction<...>` helper that will evaluate to
// a true boolean `std::integral_constant` if all passed types evaluate
// to true.
template <typename...>
struct conjunction : std::true_type
{
};
template <typename T>
struct conjunction<T> : T
{
};
template <typename T, typename... Ts>
struct conjunction<T, Ts...>
: std::conditional_t<T::value != false, conjunction<Ts...>, T>
{
};
// Define `constexpr` predicates:
template <int T>
constexpr bool is_zero_or_one()
{
return T == 0 || T == 1;
}
template <int... Ts>
constexpr bool all_are_zero_or_one()
{
// Using variadic pack expansion and `conjunction` we can
// simulate an `and` left fold over the parameter pack:
return conjunction<
std::integral_constant<bool, is_zero_or_one<Ts>()>...
>{};
}
int main()
{
static_assert(all_are_zero_or_one<0, 1, 0, 1, 0, 0>(), "");
static_assert(!all_are_zero_or_one<2, 1, 0, 1, 0, 0>(), "");
}
如果您正在寻找一种明确的方法来遍历编译时元素集合,我建议您查看以下资源:
boost::hana - 一个现代元编程库,允许使用 "traditional" 命令式语法进行编译时计算。
My CppCon 2015 talk: for_each_argument explained and expanded - 使用 std::tuple
和 "type-value encoding" 范例,您可以将编译时数值存储在元组中并在编译时对其进行迭代。我的演讲展示了一种以这种方式进行迭代的可能方式。
您可以像这样使用递归模板助手来实现静态验证。然后,当您尝试使用包含无效数字的序列编译代码时,您将收到编译器错误,如您所愿,静态断言失败。
#include <iostream>
template<typename T, T... Numbers>
struct ValidateSequence;
template<typename T>
struct ValidateSequence<T>{};
template<typename T, T Number, T... Numbers>
struct ValidateSequence<T, Number, Numbers...>
{
static_assert(Number == 0 || Number == 1, "Invalid Number");
ValidateSequence<T, Numbers...> rest;
};
template<typename T, T... Numbers>
class Sequence
{
public:
constexpr static unsigned count = sizeof...(Numbers);
constexpr static T numbers[] = {Numbers...};
ValidateSequence<T, Numbers...> validate;
};
int main()
{
Sequence <int, 1, 2, 1, 2> sec;
std::cout << sec.count << std::endl;
return 0;
}
另一种解决方案:
template<typename T>
constexpr bool IsOneOrZero(T&& t) {
return t == 0 || t == 1;
}
template<typename T, typename... Args>
constexpr bool IsOneOrZero(T&& first, Args&&... args) {
return IsOneOrZero(std::forward<T>(first)) && IsOneOrZero(std::forward<Args>(args)...);
}
template<typename T, T First, T... Numbers>
class Sequence final {
// Unpack parameter pack into a constexpr array
constexpr static T count = sizeof...(Numbers);
constexpr static T numbers[count] = { Numbers... };
static_assert(IsOneOrZero(First, Numbers...), "ERROR");
};
简单的 C++14 解决方案:
template <typename T, T ... Numbers>
class Sequence final {
static constexpr bool is_all_zero_or_one(std::initializer_list<T> list) {
for (auto elem : list) {
if (elem != 0 && elem != 1) return false;
}
return true;
}
static_assert(is_all_zero_or_one({Numbers...}),
"Only zeroes and ones are allowed.");
};
我会投入 。
template<bool...> struct bool_pack;
template<bool... bs>
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
static_assert(all_true<(Numbers == 0 || Numbers == 1)...>::value, "");
如果表达式变得复杂,将表达式提取到 constexpr
函数中。
C++11
msvc2015u3,gcc5.4,clang3.8
#include <cstdint>
#include <algorithm>
namespace utility {
template <typename T0>
inline constexpr bool is_all_true(T0 && v0)
{
return std::forward<T0>(v0) ? true : false;
}
template <typename T0, typename... Args>
inline constexpr bool is_all_true(T0 && v0, Args &&... args)
{
return (std::forward<T0>(v0) ? true : false) && is_all_true(std::forward<Args>(args)...);
}
template <typename T0>
inline constexpr bool is_all_false(T0 && v0)
{
return std::forward<T0>(v0) ? false : true;
}
template <typename T0, typename... Args>
inline constexpr bool is_all_false(T0 && v0, Args &&... args)
{
return (std::forward<T0>(v0) ? false : true) && is_all_false(std::forward<Args>(args)...);
}
template <typename T0>
inline constexpr bool is_any_true(T0 && v0)
{
return std::forward<T0>(v0) ? true : false;
}
template <typename T0, typename... Args>
inline constexpr bool is_any_true(T0 && v0, Args &&... args)
{
return (std::forward<T0>(v0) ? true : false) || is_any_true(std::forward<Args>(args)...);
}
template <typename T0>
inline constexpr bool is_any_false(T0 && v0)
{
return std::forward<T0>(v0) ? false : true;
}
template <typename T0, typename... Args>
inline constexpr bool is_any_false(T0 && v0, Args &&... args)
{
return (std::forward<T0>(v0) ? false : true) || is_any_false(std::forward<Args>(args)...);
}
}
'
#gcc, 铿锵声
static_assert(utility::is_all_true((数字 == 0 || 数字 == 1)...), "只允许使用 1 和 0。");
#msvc2015u3(解决方法:error C2059: syntax error: '...'
)
static constexpr const bool boo = utility::is_all_true((数字== 0 || 数字== 1)...);
static_assert(boo, "只允许使用 1 和 0。");
作为 tacklelib
库的一部分实现:
https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/utility/type_identity.hpp
https://github.com/andry81/tacklelib/tree/trunk/include/tacklelib/utility/type_identity.hpp
基于.
的单行c++17解决方案
#include <type_traits>
static_assert(std::conjunction<std::bool_constant<(Numbers == 0 || Numbers == 1)>...>::value, "");
实际上这可以用 c++11 完成,因为 std::conjunction 和 std::bool_constant 都是纯粹的 库特性 并且不需要任何 核心语言特性 除了 c++11.
我正在创建可变参数模板。
假设我有这样的东西:
template<typename T, T ... Numbers>
class Sequence final {
// Unpack parameter pack into a constexpr array
constexpr static T count = sizeof...(Numbers);
constexpr static T numbers[count] = { Numbers... };
// ...
}
这个 class 的实例可以像这样实例化:
Sequence<uint32_t, 1, 2, 3, 42, 25> seq;
我想在编译时使用 static_assert
确保 numbers
参数包仅包含特定数字。为了这个例子,假设我只想允许 0
或 1
.
所以我想做类似的事情:
for (size_t i = 0; i < count; i++) {
static_assert(numbers[i] == 1 || numbers[i] == 0, "Only ones and zeroes are allowed.");
}
但显然,static_assert
不适用于 for
循环。我很确定这一定有某种语法,但我还没弄明白。
我更愿意使用使用 C++11 编译器(或者 C++14 编译器,如果它在 C++11 中不可用)编译的东西。
您不能对编译时值使用传统的 for 循环,但是有许多方法可以迭代编译时集合。但是,在您的情况下,没有必要显式循环遍历每个数字:您可以使用包扩展来确保数字仅为 0
或 1
:
#include <type_traits>
// We define a `conjunction<...>` helper that will evaluate to
// a true boolean `std::integral_constant` if all passed types evaluate
// to true.
template <typename...>
struct conjunction : std::true_type
{
};
template <typename T>
struct conjunction<T> : T
{
};
template <typename T, typename... Ts>
struct conjunction<T, Ts...>
: std::conditional_t<T::value != false, conjunction<Ts...>, T>
{
};
// Define `constexpr` predicates:
template <int T>
constexpr bool is_zero_or_one()
{
return T == 0 || T == 1;
}
template <int... Ts>
constexpr bool all_are_zero_or_one()
{
// Using variadic pack expansion and `conjunction` we can
// simulate an `and` left fold over the parameter pack:
return conjunction<
std::integral_constant<bool, is_zero_or_one<Ts>()>...
>{};
}
int main()
{
static_assert(all_are_zero_or_one<0, 1, 0, 1, 0, 0>(), "");
static_assert(!all_are_zero_or_one<2, 1, 0, 1, 0, 0>(), "");
}
如果您正在寻找一种明确的方法来遍历编译时元素集合,我建议您查看以下资源:
boost::hana - 一个现代元编程库,允许使用 "traditional" 命令式语法进行编译时计算。
My CppCon 2015 talk: for_each_argument explained and expanded - 使用 std::tuple
和 "type-value encoding" 范例,您可以将编译时数值存储在元组中并在编译时对其进行迭代。我的演讲展示了一种以这种方式进行迭代的可能方式。
您可以像这样使用递归模板助手来实现静态验证。然后,当您尝试使用包含无效数字的序列编译代码时,您将收到编译器错误,如您所愿,静态断言失败。
#include <iostream>
template<typename T, T... Numbers>
struct ValidateSequence;
template<typename T>
struct ValidateSequence<T>{};
template<typename T, T Number, T... Numbers>
struct ValidateSequence<T, Number, Numbers...>
{
static_assert(Number == 0 || Number == 1, "Invalid Number");
ValidateSequence<T, Numbers...> rest;
};
template<typename T, T... Numbers>
class Sequence
{
public:
constexpr static unsigned count = sizeof...(Numbers);
constexpr static T numbers[] = {Numbers...};
ValidateSequence<T, Numbers...> validate;
};
int main()
{
Sequence <int, 1, 2, 1, 2> sec;
std::cout << sec.count << std::endl;
return 0;
}
另一种解决方案:
template<typename T>
constexpr bool IsOneOrZero(T&& t) {
return t == 0 || t == 1;
}
template<typename T, typename... Args>
constexpr bool IsOneOrZero(T&& first, Args&&... args) {
return IsOneOrZero(std::forward<T>(first)) && IsOneOrZero(std::forward<Args>(args)...);
}
template<typename T, T First, T... Numbers>
class Sequence final {
// Unpack parameter pack into a constexpr array
constexpr static T count = sizeof...(Numbers);
constexpr static T numbers[count] = { Numbers... };
static_assert(IsOneOrZero(First, Numbers...), "ERROR");
};
简单的 C++14 解决方案:
template <typename T, T ... Numbers>
class Sequence final {
static constexpr bool is_all_zero_or_one(std::initializer_list<T> list) {
for (auto elem : list) {
if (elem != 0 && elem != 1) return false;
}
return true;
}
static_assert(is_all_zero_or_one({Numbers...}),
"Only zeroes and ones are allowed.");
};
我会投入
template<bool...> struct bool_pack;
template<bool... bs>
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
static_assert(all_true<(Numbers == 0 || Numbers == 1)...>::value, "");
如果表达式变得复杂,将表达式提取到 constexpr
函数中。
C++11
msvc2015u3,gcc5.4,clang3.8
#include <cstdint> #include <algorithm> namespace utility { template <typename T0> inline constexpr bool is_all_true(T0 && v0) { return std::forward<T0>(v0) ? true : false; } template <typename T0, typename... Args> inline constexpr bool is_all_true(T0 && v0, Args &&... args) { return (std::forward<T0>(v0) ? true : false) && is_all_true(std::forward<Args>(args)...); } template <typename T0> inline constexpr bool is_all_false(T0 && v0) { return std::forward<T0>(v0) ? false : true; } template <typename T0, typename... Args> inline constexpr bool is_all_false(T0 && v0, Args &&... args) { return (std::forward<T0>(v0) ? false : true) && is_all_false(std::forward<Args>(args)...); } template <typename T0> inline constexpr bool is_any_true(T0 && v0) { return std::forward<T0>(v0) ? true : false; } template <typename T0, typename... Args> inline constexpr bool is_any_true(T0 && v0, Args &&... args) { return (std::forward<T0>(v0) ? true : false) || is_any_true(std::forward<Args>(args)...); } template <typename T0> inline constexpr bool is_any_false(T0 && v0) { return std::forward<T0>(v0) ? false : true; } template <typename T0, typename... Args> inline constexpr bool is_any_false(T0 && v0, Args &&... args) { return (std::forward<T0>(v0) ? false : true) || is_any_false(std::forward<Args>(args)...); } }
'
#gcc, 铿锵声 static_assert(utility::is_all_true((数字 == 0 || 数字 == 1)...), "只允许使用 1 和 0。");
#msvc2015u3(解决方法:error C2059: syntax error: '...'
)
static constexpr const bool boo = utility::is_all_true((数字== 0 || 数字== 1)...);
static_assert(boo, "只允许使用 1 和 0。");
作为 tacklelib
库的一部分实现:
https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/utility/type_identity.hpp
https://github.com/andry81/tacklelib/tree/trunk/include/tacklelib/utility/type_identity.hpp
基于
#include <type_traits>
static_assert(std::conjunction<std::bool_constant<(Numbers == 0 || Numbers == 1)>...>::value, "");
实际上这可以用 c++11 完成,因为 std::conjunction 和 std::bool_constant 都是纯粹的 库特性 并且不需要任何 核心语言特性 除了 c++11.