为什么 `std::initializer_list` 没有提供下标运算符?

54
假设您正在编写一个接受名为“list”的std :: initializer_list的函数,并且该函数需要随机访问列表元素。编写list [i]而不是list.begin()[i]会很方便。那么,为什么std :: initializer_list不提供operator []的定义?
我无法想到任何情况下operator []返回const T&不是良好定义的。效率似乎不是这里的问题,因为std :: initializer_list :: iterator别名为const T *,这显然是一个随机访问迭代器。

1
我猜是因为它的主要用途是作为一个按顺序处理的列表。 - PlasmaHH
2
一个常见的用例是构造函数分配一块内存并使用 allocator.construct(p, v) 构造块中的元素。虽然列表仍然按顺序处理,但外部 for 循环已经有一个计数器,适合使用 operator[] 语法。 - void-pointer
@void-pointer 在这种情况下,通常不是应该进行复制操作吗?这样就不需要循环了,而且我通常也会避免使用显式循环。不过问题仍然是有效的。 - Konrad Rudolph
1
@void-pointer:你也可以使用迭代器编写该循环,当然还有一些算法可供使用。 - PlasmaHH
2
@KonradRudolph 很好的观点,uninitialized_copy会更优雅地完成工作。Brb,我得重构一些代码=) - void-pointer
2
作为猜测,标准中的第一次迭代。保持简单?有趣的是,如果“begin”是随机的,那么.begin()[N]就会起作用。 - Yakk - Adam Nevraumont
2个回答

21
根据《C++程序设计语言(第四版)》第17.3.4.2节(第497页)的Bjarne Stroustrup的说法:
不幸的是,initializer_list不提供下标访问。
未给出进一步原因。
我猜有以下几个原因之一:
1.这是一个遗漏,或者 2.由于initializer_list类是用数组实现的,并且你必须进行边界检查来提供安全访问,如果提供该接口,它可以更容易地被不安全地使用,或者 3.为了与std算法迭代范例保持一致,或者 4.由于initializer_list本质上是临时的,直接对其进行地址解析可能会导致更多错误。
2和4听起来有些薄弱,3也是。我的钱投在1上。

19
6年后,我们是否知道答案? - jippyjoe4
3
@jippyjoe4 现在是9! :) - Hunter Kohler

7

确实有点让人烦恼,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 ++创建一些“类似于下划线”的实用库,该库将具有自己的命名空间,并重载_函数以进行各种其他有用的用途。
现在可以像这样使用新的_函数:
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]就可以工作了。 - Samuel Danielson
9
如果我在代码中看到语法错误,我会立刻"懵逼"。我甚至不知道下划线实际上是一个标识符,更不用说表示一个函数了。就像真的很让人"懵逼"。我可能最终会弄清楚它的含义(如果使用命名 _ 的标识符可以通过"转到定义"来解决?),但这并不能减轻我对编写此代码的程序员家族的强烈、迅速和重复的抱怨。我的建议是使用 list.begin()[i]。很抱歉我要给你点个踩,因为这其实是一种很酷的技巧,从语言上来说,但在实践中它会适得其反。 - bolov
我猜你实际上想要返回一个 cons T& 而不是副本。 - MikeMB
@bolov 原始 WTF 是遗漏。有这样的小问题,人们永远不会回到 CPP。 - Anona112

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接