【问题标题】:Best way to pass compile-time list of values to function?将编译时值列表传递给函数的最佳方法?
【发布时间】: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&lt;int&gt;’ is not a valid type for a template non-type parameter because it is not structural

说实话,我不知道“非结构性”是什么意思。鉴于std::initializer_list 显然完全是constexprstd::array 在相同的情况下工作,我实际上对这种方法不起作用感到惊讶和失望。标准中似乎有一个关于 std::initializer_list 字面性的错误:https://stackoverflow.com/a/28115954/1525238 无论如何,我确实认为这是一些非常酷的编译时魔法错失的机会。

问题:

您能否提出以任何可能的方式改进上述解决方案的任何方法,或提出其他解决方案?理想情况下,“最佳”解决方案将结合所有 API 和实现的简洁性和可读性,同时要求尽可能低的 c++ 标准。

【问题讨论】:

  • 您需要定义“最佳”,否则这是基于意见的。
  • 在(1)中,您可以使用折叠表达式来避免递归,也可以使用包初始化数组并正常迭代。
  • std::initializer_list&lt;T&gt; 现在绝对不能用作非类型模板参数(并且可能永远不会),如果是这样,它可能无论如何都不会做你所期望的,因为模板等价不仅仅是==。所以(4)显然不是一个解决方案。
  • @Barry 需要详细说明吗?在这种情况下,initializer_listarray 在语义上有何区别?尤其是“非结构性”一词在这一点上对我来说没有任何意义。
  • @AyberkÖzgür array 拥有其数据,initializer_list 不拥有。 “结构”是“可用作非类型模板参数”的语言术语,定义为here

标签: c++ c++20 constexpr compile-time


【解决方案1】:

感谢 HolyBlackCat 建议从包中初始化 std::array,我不知道这是可能的:

template <int... numbers>
constexpr int sum() {
    std::array<int, sizeof...(numbers)> arr = {numbers...};
    int ret = 0;
    for(int number : arr)
        ret += number;
    return ret;
}

//API:

template <int... numbers>
inline void add(int& target) {
    target += sum<numbers...>();
}
...
int number = 0;
add<1, 2, 3, 4, 5>(number);

这使得实现更加简洁。需要 c++17。

【讨论】:

  • sum的实现中不需要使用std::array,使用fold expressions就简单多了,比如return (numbers + ... + 0);
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多