【问题标题】:Compile-time concatenation of std::initializer_list'sstd::initializer_list 的编译时串联
【发布时间】:2022-11-16 05:23:50
【问题描述】:

我想写一些代码如下:

using int_list_t = std::initializer_list<int>;

struct ThreeDimensionalBox {
  static constexpr int_list_t kDims = {1, 2, 3};
};

struct FourDimensionalBox {
  static constexpr int_list_t kDims = {4, 5, 6, 7};
};

template<typename Box1, typename Box2>
struct CombinedBox {
  static constexpr int_list_t kDims = Box1::kDims + Box2::kDims;  // error
};

using SevenDimensionalBox = CombinedBox<ThreeDimensionalBox, FourDimensionalBox>;

有什么方法可以修复CombinedBox 的实现,使SevenDimensionalBox::kDims 有效绑定到{1, 2, 3, 4, 5, 6, 7}

我知道我可以用带有可变 int 模板参数列表的自定义模板类替换 std::initializer_list&lt;int&gt;,并通过标准元编程递归技术有效地实现串联。我只是想知道是否存在仅使用 std::initializer_list 的解决方案。

【问题讨论】:

  • 你为什么在这里使用std::initializer_list?这不是它的意思,你很容易导致悬空引用。您可能应该改用 std::array

标签: c++ template-meta-programming initializer-list


【解决方案1】:

std::initializer_list 只能初始化为空,用大括号括起来的元素列表,或者通过复制。

然而,即使使用复制构造,std::initializer_list 引用的实际数组的生命周期也取决于原始 std::initializer_list 对象的生命周期,该对象由大括号括起来的元素列表初始化。换句话说,复制 std::initializer_list 不会复制数组或延长其生命周期。

因此不可能连接std::initializer_lists。它们并不意味着用作容器。 std::initializer_list 应该主要仅用作函数参数,作为一种轻量级方式,将未指定大小的大括号括起来的元素列表传递给函数,以便函数进一步处理元素。

您可能需要 std::array 来代替。像这样的东西(需要 C++17,但不是 C++20):

struct ThreeDimensionalBox {
  static constexpr auto kDims = std::array{1, 2, 3};
};

struct FourDimensionalBox {
  static constexpr auto kDims = std::array{4, 5, 6, 7};
};

template<typename T, std::size_t N, std::size_t M>
constexpr auto concat_arrays(const std::array<T, N>& a, const std::array<T, M>& b) {
    // assumes `T` is default-constructible
    std::array<T, N+M> r;
    std::copy(std::begin(a), std::end(a), std::begin(r));
    std::copy(std::begin(b), std::end(b), std::begin(r)+N);
    return r;
}

template<typename Box1, typename Box2>
struct CombinedBox {
  static constexpr auto kDims = concat_arrays(Box1::kDims, Box2::kDims);
};

using SevenDimensionalBox = CombinedBox<ThreeDimensionalBox, FourDimensionalBox>;

【讨论】:

  • 换句话说,复制不会复制数组或延长其生命周期。只有某些时候。除了从数组中初始化一个 initializer_list 对象会延长数组的生命周期,这与将引用绑定到临时对象完全一样来源:en.cppreference.com/w/cpp/utility/initializer_list
  • @NathanOliver 现在应该好多了。
【解决方案2】:

std::initializer_list 完全只有一个目的:成为初始化对象的工具,在 braced-init-list({} 中的内容)用于初始化该对象的范围内。这种类型的一切都是为此目的而构建的,它有一堆防火墙来防止你用它做任何其他事情。

其中最重要的是 initializer_list 实际上并没有存储任何东西;它参考存储在别处的值数组(即创建它的 {} 的引用)。因为它不存储数组,而且它们只是从 {} 语法构造构建的,所以你不能给它们一个数组来引用。他们只能通过复制从现有的 initializer_list 对象中获取一个数组,而那个只能从 {} 或现有的 initializer_list 中获取一个数组,而后者只能从中获取一个......好吧,你明白了.

如果您需要进行编译时数组操作,std::array 是更好的工具。如果你想连接两个大括号初始化列表并使用结果来初始化某个对象......你不能那样做。

不要尝试将 initializer_list 用作快速而肮脏的数组类型。仅将其用于初始化对象。

【讨论】:

    猜你喜欢
    • 2021-06-04
    • 2017-09-12
    • 2020-09-16
    • 1970-01-01
    • 2016-08-10
    • 1970-01-01
    • 1970-01-01
    • 2013-01-11
    • 2013-08-12
    相关资源
    最近更新 更多