为啥 `std::initializer_list` 不提供下标运算符?

Posted

技术标签:

【中文标题】为啥 `std::initializer_list` 不提供下标运算符?【英文标题】:Why doesn't `std::initializer_list` provide a subscript operator?为什么 `std::initializer_list` 不提供下标运算符? 【发布时间】:2013-07-21 04:15:17 【问题描述】:

假设您正在编写一个接受名为liststd::initializer_list 的函数,并且该函数需要随机访问list 的元素。写list[i] 而不是list.begin()[i] 会很方便。那么为什么std::initializer_list 不提供operator[] 的定义呢?

我想不出operator[] 返回const T& 的任何情况都没有明确定义。效率似乎不是这里的问题,因为 std::initializer_list<T>::iterator 被别名为 const T*,这显然是一个随机访问迭代器。

【问题讨论】:

我猜是因为它的主要用例是一个按顺序处理的列表。 一个常见的用例是一个构造函数,它分配一块内存并使用allocator.construct(p, v)构造块中的元素。虽然列表仍在按顺序处理,但外部 for 循环已经有一个计数器,适合 operator[] 语法。 @void-pointer: 你也可以用迭代器编写循环,当然也有一些算法可以使用 @KonradRudolph 好点,uninitialized_copy 会更优雅地完成这项工作。 Brb,必须重构一些代码 =) 作为猜测,标准中的第一次迭代。把事情简单化?有趣的是,如果begin 是随机的,.begin()[N] 就可以工作。 【参考方案1】:

根据 Bjarne Stroustrup 在 The C++ Programming Language, 4th Edition 的第 17.3.4.2 节(第 497 页)中:

很遗憾,initializer_list 不提供下标。

没有给出进一步的理由。

我的猜测是以下原因之一:

    这是一个遗漏,或者 因为 initializer_list 类是使用数组实现的,并且您必须进行边界检查以提供安全访问,所以如果提供了该接口,它可能更容易被不安全地使用,或者 与标准算法迭代范式保持一致,或 因为 initializer_lists 本质上是临时的,所以直接处理它们有更大的出错空间

2 和 4 听起来有点弱。和 3 一样。我的钱在 1 上。

【讨论】:

6年后,我们知道答案了吗?【参考方案2】:

确实有点烦人,std::initializer_list 没有方括号运算符,因为需要对特定索引进行随机直接访问是合理的场景。

但是,可以通过一些简单的代码添加此功能:

// the class _init_list_with_square_brackets provides [] for initializer_list
template<class T>
struct _init_list_with_square_brackets 
    const std::initializer_list<T>& list;
    _init_list_with_square_brackets(const std::initializer_list<T>& _list): list(_list) 
    T operator[](unsigned int index) 
        return *(list.begin() + index);
     
;

// a function, with the short name _ (underscore) for creating 
// the _init_list_with_square_brackets out of a "regular" std::initializer_list
template<class T>
_init_list_with_square_brackets<T> _(const std::initializer_list<T>& list) 
    return _init_list_with_square_brackets<T>(list);

现在我们有了一个名为 _(下划线)的新全局函数,这对于全局 C++ 方法来说可能不是一个好名字,除非我们想为 C++ 创建一些“类似 undescore”的实用程序库,它会有自己的命名空间,为所有其他有用的用途重载 _ 函数。

现在可以像这样使用新的_函数:

void f(std::initializer_list<int> list) 
    cout << _(list)[2]; // subscript-like syntax for std::initializer_list!


int main() 
    f(1,2,3); // prints: 3
    cout << _(1,2,3)[2]; // works also, prints: 3
    return 0;

需要注意的是,如果您运行 std::initializer_list 的许多项,上面提供的解决方案在性能方面并不是一个很好的交易,因为上述建议类型的临时对象_init_list_with_square_brackets 被重复创建。当然,这再次引发了为什么标准本身没有提供这一点的疑问。

【讨论】:

很好。另一种劫持@Yakk 对该问题的评论是“auto mylist = list.begin()”然后 mylist[n] 起作用。 如果我在代码中看到语法,我会立即成为“wtf”。我不知道_ 实际上是一个id,而不是表示一个函数。像 rly,“wtf”。我最终可能会弄清楚(“去定义”是否适用于名为 _ 的 id ??),但这无助于减轻我对编写代码的家庭的强烈、快速和反复提及那。我的建议只是使用list.begin()[i]。我很抱歉投反对票,因为从语言角度来看,这是一个很酷的技巧,但在实践中它会适得其反。 我猜你实际上想返回一个cons T&amp; 而不是一个副本。 @bolov 原来的 wtf 是遗漏的。人们永远不会带着这样的小事回到 cpp。

以上是关于为啥 `std::initializer_list` 不提供下标运算符?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 std::min(std::initializer_list<T>) 按值接受参数?

为啥具有默认参数 std::initializer_list 的 ctor 不可用(VS2019)?

为啥在使用大括号初始值设定项列表时首选 std::initializer_list 构造函数?

为啥我的编译器无法计算出这种转换,它何时存在?

std::initializer_list 作为函数参数

如何将 C 数组转换为 std::initializer_list?