【问题标题】:Why does std::min(std::initializer_list<T>) take arguments by value?为什么 std::min(std::initializer_list<T>) 按值接受参数?
【发布时间】:2026-02-16 11:50:01
【问题描述】:

阅读this question 的答案后,我惊讶地发现std::min(std::initializer_list&lt;T&gt;) 按值接受它的参数。

如果您按照其名称所暗示的方式使用std::initializer_list,即作为某个对象的初始化程序,我知道我们不关心复制其元素,因为无论如何它们都会被复制以初始化对象。但是,在这种情况下,我们很可能不需要任何副本,因此如果可能的话,将参数设为 std::initializer_list&lt;const T&amp;&gt; 似乎更合理。

这种情况的最佳做法是什么?如果您不想制作不必要的副本,您是否应该不调用initializer_list 版本的std::min,或者是否有其他技巧可以避免复制?

【问题讨论】:

  • AFAIK,使用引用比传递 int 的副本需要更多的 CPU 周期,并且比传递更小的变量需要更多的内存(至少在 x86 上)。
  • 我认为原因在N2722 中有所描述,尽管我不愿意相信性能比较和结论。可能会有更多的讨论记录在某个地方。
  • @dyp 顺便说一句,在 n2772_fix 中它说可变参数模板优先于 initializer_list。为什么标准委员会决定用 initializer_list 而不是可变参数模板定义 std::min?
  • @dyp:链接已损坏,论文现在在这里:open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2722.pdf

标签: c++ c++11 std min c++-standard-library


【解决方案1】:

没有std::initializer_list&lt;const T&amp;&gt; 这样的东西。初始化列表只能按值保存对象。

参见[dcl.init.list]/5:

std::initializer_list&lt;E&gt; 类型的对象是从初始化列表构造的,就好像实现分配了一个包含 N 个 const E 类型元素的临时数组,其中 N 是初始化列表中的元素数。

没有引用数组,因此也不能有 initializer_list 的引用。

在这种情况下,我的建议是为 std::min 编写一个替代品,它采用转发引用的可变参数模板。这行得通(尽管我确信它可以改进):

template<typename T, typename... Args>
T vmin( T arg1, Args&&... args )
{
    T *p[] = { &arg1, &args... };

    return **std::min_element( begin(p), end(p), 
            [](T *a, T *b) { return *a < *b; } );
}

See it working - 使用 minmmin,制作两个额外的副本(用于 ab)。

【讨论】:

    【解决方案2】:

    总结一下cmets:

    std::initializer_list 应该是cppreference.com 中描述的轻量级代理。 因此,初始化列表的副本应该非常快,因为不会复制底层元素:

    C++11 §18.9/2

    initializer_list&lt;E&gt; 类型的对象提供对const E. 类型对象数组的访问 [ 注意: 一对指针或一个指针加上一个长度将是initializer_list 的明显表示。 initializer_list 用于实现 8.5.4 中指定的初始化列表。复制初始化列表不会复制底层元素。 — 尾注 ]

    虽然使用引用会归结为使用指针,因此需要额外的间接。

    因此,std::min 的实际问题不是它按值获取 initializer_list,而是如果 initializer_list 的参数在运行时计算,则必须复制它们。 [1] 不幸的是,发现 [1] 的基准被打破了 later

    auto min_var = std::min({1, 2, 3, 4, 5}); // fast
    auto vec = std::vector<int>{1, 2, 3, 4, 5};
    min_var = std::min({vec[0], vec[1], vec[2], vec[3], vec[4]}); // slow
    

    [1]:N2722 p。 2

    【讨论】:

    • 我认为您完全误解了问题 cmets。他们抱怨initializer_list 中的元素是在运行时复制的,而不是有initializer_list 的引用。
    • 好点!我忽略了“显示另外 8 个 cmets”链接,一旦我看到它就会导致编辑混乱。虽然我不会说我误解了这个问题。因为初始化器列表在创建时不需要创建副本。这取决于情况。
    • 您的“基准”似乎声称复制 int 很慢。它不应该是大型复制对象的向量吗? (好吧,我看到链接的文章确实有这个,每个都是百万元素的std::list
    • 这不是一个基准,而是一个示例来说明问题并保持代码小。
    最近更新 更多