C++11 删除了所有容器的值类型是 CopyConstructible 和 Assignable 的要求(尽管容器上的特定操作可能会施加这些要求)。从理论上讲,这应该可以定义例如 ,std::deque<const Foo>
这在 C++03 中是不可能的。
出乎意料的是,当我尝试这样做时,gcc 4.7.2 产生了它通常吐出的难以理解的错误 [1],但 clang 至少使错误可读,并且使用 libc++ 编译它时没有错误。
现在,当两个不同的编译器产生不同的结果时,我总是想知道正确的答案是什么,所以我搜索了所有我能找到的关于 const/assignable/value types/containers 等的引用。我发现几乎十年来非常相似的问题和答案,其中一些在 SO 上,其他在各种 C++ 邮件列表中,在其他地方,包括 Gnu buganizer,所有这些基本上都可以总结为以下对话。
问:为什么我不能申报std::vector<const int>
(作为一个简化的例子)
A:你到底为什么要这么做?这是荒谬的。
问:嗯,这对我来说很有意义。为什么我做不到?
答:因为标准要求值类型是可分配的。
问:但我不打算分配他们。在我创建它们之后,我希望它们成为 const 。
答:这不是它的工作方式。下一个问题!
伴随着温和的冲刺:
A2:C++11 已经决定允许这样做。你只需要等待。与此同时,重新考虑你荒谬的设计。
这些似乎不是很有说服力的答案,尽管我可能有偏见,因为我属于“但对我来说有意义”的类别。就我而言,我希望有一个类似堆栈的容器,其中推入堆栈的东西在弹出之前是不可变的,这对我来说并不是一件特别奇怪的事情,希望能够用一种类型来表达系统。
无论如何,我开始思考答案,“标准要求所有容器的值类型都是可分配的。” 而且,据我所知,现在我找到了 C++03 标准草案的旧副本,这是真的;它做了。
另一方面, is 的值类型在std::map
我看来不像是可分配的。尽管如此,我还是再次尝试了,并且 gcc 继续编译它而没有眨眼。所以至少我有某种解决方法。std::pair<const Key, T>
std::deque<std::tuple<const Foo>>
然后我尝试打印出std::is_assignable<const Foo, const Foo>
and的值std::is_assignable<std::tuple<const Foo>, const std::tuple<const Foo>>
,结果发现前者被报告为不可分配,正如您所期望的,但后者被报告为可分配(由 clang 和 gcc 报告)。当然,它并不是真正可分配的。尝试编译a = b;
被 gcc 拒绝并抱怨error: assignment of read-only location
(这只是我在此任务中遇到的唯一错误消息,实际上很容易理解)。但是,如果不尝试进行分配,clang 和 gcc 都同样乐意实例化deque<const>
,并且代码似乎运行良好。
现在,如果std::tuple<const int>
真的是可分配的,那么我不能抱怨C++03
标准中的不一致——而且,真的,谁在乎——但我发现两个不同的标准库实现报告一个类型实际上是可分配的,这令人不安,试图分配给它的引用将导致(非常明智的)编译器错误。我可能在某个时候想在模板 SFINAE 中使用测试,根据我今天看到的,它看起来不太可靠。
那么有没有人可以阐明这个问题(在标题中):Assignable 的真正含义是什么?还有两个额外的问题:
1) 委员会是否真的打算允许实例化具有const
值类型的容器,或者他们是否有其他不可分配的情况?
const Foo
2)和的常数之间真的存在显着差异std::tuple<const Foo>
吗?
[1] 对于真正好奇的人,这里是 gcc 在尝试编译声明时产生的错误消息std::deque<const std::string>
(添加了一些行尾,如果向下滚动足够远,则会给出解释):
In file included from /usr/include/x86_64-linux-gnu/c++/4.7/./bits/c++allocator.h:34:0,
from /usr/include/c++/4.7/bits/allocator.h:48,
from /usr/include/c++/4.7/string:43,
from /usr/include/c++/4.7/random:41,
from /usr/include/c++/4.7/bits/stl_algo.h:67,
from /usr/include/c++/4.7/algorithm:63,
from const_stack.cc:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<const std::basic_string<char> >’:
/usr/include/c++/4.7/bits/allocator.h:89:11: required from ‘class std::allocator<const std::basic_string<char> >’
/usr/include/c++/4.7/bits/stl_deque.h:489:61: required from ‘class std::_Deque_base<const std::basic_string<char>, std::allocator<const std::basic_string<char> > >’
/usr/include/c++/4.7/bits/stl_deque.h:728:11: required from ‘class std::deque<const std::basic_string<char> >’
const_stack.cc:112:27: required from here
/usr/include/c++/4.7/ext/new_allocator.h:83:7:
error: ‘const _Tp* __gnu_cxx::new_allocator< <template-parameter-1-1> >::address(
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference) const [
with _Tp = const std::basic_string<char>;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_pointer =
const std::basic_string<char>*;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference =
const std::basic_string<char>&]’ cannot be overloaded
/usr/include/c++/4.7/ext/new_allocator.h:79:7:
error: with ‘_Tp* __gnu_cxx::new_allocator< <template-parameter-1-1> >::address(
__gnu_cxx::new_allocator< <template-parameter-1-1> >::reference) const [
with _Tp = const std::basic_string<char>;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::pointer = const std::basic_string<char>*;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::reference = const std::basic_string<char>&]’
所以这里发生的事情是标准(第 20.6.9.1 节)坚持默认分配器具有成员函数:
pointer address(reference x) const noexcept;
const_pointer address(const_reference x) const noexcept;
但是如果您使用const
模板参数(显然是 UB)实例化它,那么reference
和const_reference
是相同的类型,因此声明是重复的。(定义的主体是相同的,因为它的价值。)因此,没有分配器感知容器可以处理显式const
值类型。隐藏const
内部 atuple
允许分配器实例化。标准中的这个分配器要求被用来证明至少关闭几个关于 std::vector<const int>
. libc++ 也以一种明显简单的方式解决了这个问题,即提供一种特殊化的方法,allocator<const T>
删除了重复的函数声明。