为啥 `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 【问题描述】:假设您正在编写一个接受名为list
的std::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&
而不是一个副本。
@bolov 原来的 wtf 是遗漏的。人们永远不会带着这样的小事回到 cpp。以上是关于为啥 `std::initializer_list` 不提供下标运算符?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 std::min(std::initializer_list<T>) 按值接受参数?
为啥具有默认参数 std::initializer_list 的 ctor 不可用(VS2019)?