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>
(作为简化示例)
答:你为什么要这样做?这是荒谬的。
问:嗯,这对我来说很有道理。为什么我不能这样做?答:因为标准要求值类型可分配。
问:但我不打算分配它们。我希望它们在我创建它们之后成为const。答:这不是它的工作方式。下一个问题!
带着温和的潇洒:
A2:C ++ 11决定允许这样做。你只需要等待。与此同时,重新思考你的荒谬设计。
这些似乎不是非常引人注目的答案,尽管我可能有偏见,因为我属于“但这对我有意义”的范畴。在我的情况下,我想有一个类似堆栈的容器,其中被推入堆栈的东西是不可变的,直到它们被弹出,这不会让我感到特别奇怪的是希望能够用类型表达系统
无论如何,我开始考虑答案,“标准要求所有容器的值类型都可以分配。”而且,据我所知,现在我发现了C ++ 03标准草案的旧版本,这是真的;它确实。
另一方面,std::map
的值类型是std::pair<const Key, T>
,它看起来并不像我可分配的那样。尽管如此,我还是再次尝试了std::deque<std::tuple<const Foo>>
,然后gcc继续编译它而不用眨眼。所以至少我有一些解决方法。
然后我尝试打印出std::is_assignable<const Foo, const Foo>
和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
值类型的容器,或者他们是否还考虑了其他一些不可分配的案例?和
2)const Foo
和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
隐藏在tuple
内允许分配器实例化。标准中的这个分配器要求被用来证明关于std::vector<const int>
的问题至少关闭了几个旧的libstdc ++错误,尽管它并没有把我作为一个坚实的原则。此外,libc ++以明显简单的方式解决问题,即提供allocator<const T>
的特化,删除重复的函数声明。
答案 0 :(得分:12)
在C ++ 03中,Assignable
由§23.1/ 4中的表64定义,
Expression Return type Post-condition t = u T& t is equivalent to u
一方面,std::map
未满足此要求。另一方面,对std::list
的要求过于严格。而C ++ 11表明,std::vector
通常不需要它,但是通过使用某些操作(例如赋值)来强加它。
在C ++ 11中,相应的要求名为CopyAssignable
,由§17.6.3.1/ 2中的表23定义,
Expression Return type Return value Post-condition t = v T& t t is equivalent to v, the value of v is unchanged
主要区别在于容器元素不再需要CopyAssignable
,而且还有相应的要求MoveAssignable
。
无论如何,具有const
数据成员的结构显然不可转让,除非选择用非常特殊的解释来阅读“等同于”。
据我所知,C ++ 11中唯一与操作无关的元素类型要求(来自§23.2.1/ 4中的表96)必须是Destructible
。
关于std::is_assignable
,它并未完全测试CopyAssignable
标准。
根据C ++11§20.9.4.3/ 3中的表49,这是std::is_assignable<T, U>
所暗示的内容:
“表达
declval<T>() = declval<U>()
是 处理后形成良好 作为一个未经评估的操作数 (第5条)。访问 检查就像执行一样 在与T
无关的上下文中 和U
。只有有效期 直接的背景 赋值表达式 被认为。 [注意: 汇编 表达式可以导致 副作用如 类的实例化 模板专业化 和功能模板 专业化, 一代人 隐式定义 功能等。这样 副作用不在 “直接背景”和 可以导致该计划 形象不对。 末端 注意]“
本质上,这意味着operator=
的访问/存在+参数类型兼容性检查,仅此而已。
但是,Visual C ++ 11.0似乎没有进行访问检查,而g ++ 4.7.1对它进行了扼流:
#include <iostream>
#include <type_traits>
#include <tuple>
using namespace std;
struct A {};
struct B { private: B& operator=( B const& ); };
template< class Type >
bool isAssignable() { return is_assignable< Type, Type >::value; }
int main()
{
wcout << boolalpha;
wcout << isAssignable< A >() << endl; // OK.
wcout << isAssignable< B >() << endl; // Uh oh.
}
使用Visual C ++ 11.0构建:
[D:\dev\test\so\assignable] > cl assignable.cpp assignable.cpp [D:\dev\test\so\assignable] > _
使用g ++ 4.7.1构建:
[D:\dev\test\so\assignable] > g++ assignable.cpp d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In substitution of 'template static decltype (((declval)()=(declval)(), std::__sfinae_types::__one()))std::__is_assignable_helper::__test(int) [with _ Tp1 = _Tp1; _Up1 = _Up1; _Tp = B; _Up = B] [with _Tp1 = B; _Up1 = B]': d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: required from 'constexpr const bool std::__is_assignable_helper::value' d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable' assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]' assignable.cpp:16:32: required from here assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40, from assignable.cpp:1: d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1049:2: error: within this context d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In substitution of 'template static decltype (((declval)()=(declval)(), std::__sfinae_types::__one())) std::__is_assignable_helper::__test(int) [with _ Tp1 = _Tp1; _Up1 = _Up1; _Tp = B; _Up = B] [with _Tp1 = B; _Up1 = B]': d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: required from 'constexpr const bool std::__is_assignable_helper::value' d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable' assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]' assignable.cpp:16:32: required from here assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40, from assignable.cpp:1: d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1049:2: error: within this context d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In instantiation of 'constexpr const bool std::__is_assignable_helper::value': d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable' assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]' assignable.cpp:16:32: required from here assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40, from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40, from assignable.cpp:1: d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: error: within this context [D:\dev\test\so\assignable] > _
总而言之,标准std::is_assignable
似乎效用非常有限,在撰写本文时,它不能用于可移植代码。
编辑:用正确的<utility>
替换<type_traits
。有趣的是,它与g ++无关。甚至不是错误消息,所以我只是让它保持原样。
答案 1 :(得分:4)
我将此问题发给Alf,但我想添加一些注释以供将来参考。
正如Alf所说,std::is_*_assignable
实际上只检查适当赋值运算符的存在(显式或隐式)。如果实例化,它们不一定检查它是否形成良好。这适用于默认赋值运算符。如果有成员声明为const
,则将删除默认赋值运算符。如果基类具有已删除的赋值运算符,则将删除默认赋值运算符。所以,如果你只是让默认值做他们的事情,它应该没问题。
但是,如果您确实声明operator=
,则您有责任(如果您愿意)确保将其妥善删除。例如,这将编译并运行(至少使用clang),并报告C
is_assignable:
#include <iostream>
#include <type_traits>
#include <tuple>
using namespace std;
struct A { const int x; A() : x() {}};
struct C {
struct A a;
C& operator=( C const& other);
};
template< class Type >
bool isAssignable() { return is_assignable< Type&, const Type& >::value; }
int main()
{
wcout << boolalpha;
wcout << isAssignable< A >() << endl; // false
wcout << isAssignable< C >() << endl; // true
C c1;
C c2;
}
在链接时间之前不会记录赋值运算符定义的缺失,在这种情况下根本不会记录,因为从不使用赋值运算符。但请注意,允许在C::operator=
上使用std::is_assignable
条件进行编译。当然,我无法以导致分配给其成员C::operator=
的方式定义a
,因为该成员不可分配。
但这不是一个特别有趣的例子。有趣的是使用模板,例如启动整个问题的std::tuple
问题。让我们在上面添加几个模板,并通过赋予其成员C::operator=
来实际定义a
:
using namespace std;
template<bool> struct A {
A() : x() {}
const int x;
};
template<bool B> struct C {
struct A<B> a;
C& operator=( C const& other) {
this->a = other.a;
return *this;
}
};
template< class Type >
bool isAssignable() { return is_assignable< Type&, const Type& >::value; }
int main()
{
wcout << boolalpha;
wcout << isAssignable< A<false> >() << endl; // false
wcout << isAssignable< C<false> >() << endl; // true
C<false> c1;
C<false> c2;
c1 = c2; // Bang
return 0;
}
在最后没有赋值,代码编译并运行(在clang 3.3下)并报告A<false>
不可分配(正确)但C<false>
可分配(惊讶!)。使用C::operator=
的实际尝试揭示了真相,因为直到那一点,编译器才试图实际实例化该运算符。到那时为止,通过is_assignable
的实例化,运算符只是一个公共接口的声明,正如Alf所说的那样 - std::is_assignable
正在寻找的所有内容。
呼。
所以底线,我认为对于标准聚合对象而言,这是标准库和标准库实现中的缺陷,如果任何组件类型不可分配,则应删除operator=
。对于std::tuple
,§20.4.2.2列出了operator=
的所有组件类型可分配的要求,并且对其他类型有类似的要求,但我不认为该要求要求库实现者删除不适用operator=
。
但是,据我所知,没有什么能阻止库实现删除(除了有条件地删除赋值运算符的烦恼因素)。在我看来这几天后,他们应该这样做,而且标准应该要求他们这样做。否则,可靠地使用is_assignable
就不可能了。