"可分配"究竟意味着什么?

ric*_*ici 26 c++ language-lawyer c++11

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就是不看我喜欢它的分配.尽管如此,我还是再次尝试,然后gcc继续编写它而不用眨眼.所以至少我有一些解决方法.std::pair<const Key, T>std::deque<std::tuple<const Foo>>

然后我试图打印出的价值std::is_assignable<const Foo, const Foo>std::is_assignable<std::tuple<const Foo>, const std::tuple<const Foo>>,而且事实证明,前者被报告为不分配,如你所期望的,但后者报告为分配后(通过铛和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)是否有真正的constnesses之间的差异显著const Foostd::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>&]’
Run Code Online (Sandbox Code Playgroud)

那么这里发生的是标准(第20.6.9.1节)坚持默认分配器具有成员函数:

pointer address(reference x) const noexcept;
const_pointer address(const_reference x) const noexcept;
Run Code Online (Sandbox Code Playgroud)

但如果你用它实例化const模板参数(这显然是UB),然后referenceconst_reference属于同一类型,因此声明被复制.(定义的主体是相同的,因为它的价值.)因此,没有分配器感知的容器可以处理显式const值类型.隐藏const内部tuple允许分配器实例化.标准中的这个分配器要求被用来证明关闭至少几个关于问题的旧libstdc ++错误是合理的 std::vector<const int>,尽管它并没有把我当作一个坚实的原则.此外,libc ++以明显简单的方式解决问题,即提供allocator<const T>删除了重复函数声明的特殊化.

Che*_*Alf 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标准.

std::is_assignable<T, U>根据C++11§20.9.4.3/ 3中的表49,这意味着:

" 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.
}
Run Code Online (Sandbox Code Playgroud)

使用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 ++无关.甚至没有错误信息,所以我只是让它成为现实.

  • FWIW您的程序被即时GCC 4.8的快照接受,产生预期结果(即输出'true'然后'false'). (2认同)