【发布时间】:2021-12-29 16:25:51
【问题描述】:
我正在尝试找到将编译时值列表传递给实用程序函数的最佳方法,作为基于真实用例的练习。该列表将进行一系列操作,结果值将用于运行时的另一个操作。以下是我找到的一些解决方案,简化为 MWE。
当然,实际用例中的操作要复杂得多,因此需要这些实用函数。
方案一:参数包
template <int number>
constexpr int sum() {
return number;
}
template <int number, int next, int... rest>
constexpr int sum() {
return number + sum<next, rest...>();
}
//API:
template <int... numbers>
inline void add(int& target) {
target += sum<numbers...>();
}
...
int number = 0;
add<1, 2, 3, 4, 5>(number);
优点:
- 清洁 API
- 只需要 c++14
缺点:
- 带有递归的笨拙实现,当操作复杂时设计和阅读起来很痛苦
解决方案 2:std::array
template <size_t N, std::array<int, N> numbers>
constexpr int sum() {
int ret = 0;
for (int number : numbers)
ret += number;
return ret;
}
//API:
template <size_t N, std::array<int, N> numbers>
inline void add(int& target) {
target += sum<N, numbers>();
}
...
int number = 0;
add<5, std::array{1, 2, 3, 4, 5}>(number);
优点:
- 简洁易读的实现,无论操作多么复杂,都易于设计
缺点:
- 超级笨重的 API,列表的大小必须单独指定
- 需要 c++20 才能将内联
std::array作为非类型模板参数传递
解决方案 3:std::array 包装器
template <size_t N>
struct IntArray {
constexpr IntArray(std::array<int, N> arr_) : arr(arr_) {}
const std::array<int, N> arr;
};
template <IntArray numbers>
constexpr int sum() {
int ret = 0;
for (int number : numbers.arr)
ret += number;
return ret;
}
//API:
template <IntArray numbers>
inline void add(int& target) {
target += sum<numbers>();
}
...
int target = 0;
add<IntArray<5>({1, 2, 3, 4, 5})>(target);
优点:
- 简洁易读的实现,无论操作多么复杂,都易于设计
缺点:
- (可以说)更少但仍然笨重的 API,列表的大小必须单独指定
- 需要 c++20 能够将内联
IntArray作为非类型模板参数传递,并且至少能够在函数定义中省略IntArray模板参数值
解决方案 4:std::initializer_list
template <std::initializer_list<int> numbers>
constexpr int sum() {
int ret = 0;
for (int number : numbers)
ret += number;
return ret;
}
template <std::initializer_list<int> numbers>
inline void add(int& target) {
target += sum<numbers>();
}
...
int target = 0;
add<{1, 2, 3, 4, 5}>(target);
优点:
- 简洁易读的实现,无论操作多么复杂,都易于设计
- 干净、可用和可读的 API
缺点:
- 实际上并没有编译(g++ 10.3.0 和
gnu++2a):‘std::initializer_list<int>’ is not a valid type for a template non-type parameter because it is not structural
说实话,我不知道“非结构性”是什么意思。鉴于std::initializer_list 显然完全是constexpr 和std::array 在相同的情况下工作,我实际上对这种方法不起作用感到惊讶和失望。标准中似乎有一个关于 std::initializer_list 字面性的错误:https://stackoverflow.com/a/28115954/1525238 无论如何,我确实认为这是一些非常酷的编译时魔法错失的机会。
问题:
您能否提出以任何可能的方式改进上述解决方案的任何方法,或提出其他解决方案?理想情况下,“最佳”解决方案将结合所有 API 和实现的简洁性和可读性,同时要求尽可能低的 c++ 标准。
【问题讨论】:
-
您需要定义“最佳”,否则这是基于意见的。
-
在(1)中,您可以使用折叠表达式来避免递归,也可以使用包初始化数组并正常迭代。
-
std::initializer_list<T>现在绝对不能用作非类型模板参数(并且可能永远不会),如果是这样,它可能无论如何都不会做你所期望的,因为模板等价不仅仅是==。所以(4)显然不是一个解决方案。 -
@Barry 需要详细说明吗?在这种情况下,
initializer_list和array在语义上有何区别?尤其是“非结构性”一词在这一点上对我来说没有任何意义。 -
@AyberkÖzgür
array拥有其数据,initializer_list不拥有。 “结构”是“可用作非类型模板参数”的语言术语,定义为here
标签: c++ c++20 constexpr compile-time