c++ - "Assignable"到底是什么意思?

标签 c++ c++11 language-lawyer

C++11 取消了所有容器的值类型为 CopyConstructible 和 Assignable 的要求(尽管对容器的特定操作可能会强加这些要求)。从理论上讲,这应该可以定义,例如,std::deque<const Foo> ,这在 C++03 中是不可能的。

出乎意料的是,当我尝试这个时,gcc 4.7.2 产生了它通常令人难以理解的错误 [1] 的呕吐物,但是 clang 至少使错误可读,并且使用 libc++ 编译它没有错误。

现在,当两个不同的编译器产生不同的结果时,它总是让我想知道正确答案是什么,所以我搜索了所有我能找到的对 const/assignable/value 类型/容器等的引用。我发现几乎十年来非常相似的问题和答案,其中一些在 SO 上,其他在各种 C++ 邮件列表中,以及其他地方,包括 Gnu buganizer,所有这些基本上都可以总结为以下对话。

问:为什么我不能申报std::vector<const int> (作为一个简化的例子)

A:你到底为什么要这么做?这是荒谬的。

问:嗯,这对我来说很有意义。为什么我做不到?

答:因为标准要求值类型是可赋值的。

问:但我不打算分配它们。我希望它们在我创建后成为 const。

答:这不是它的工作方式。下一个问题!

带有轻微的冲击:

A2:C++11 决定允许这样做。你只需要等待。与此同时,重新考虑你荒谬的设计。

这些似乎不是很有说服力的答案,尽管我可能有偏见,因为我属于“但对我来说很有意义”的类别。就我而言,我希望有一个类似堆栈的容器,其中压入堆栈的东西在被弹出之前是不可变的,这并没有让我觉得想要能够用类型表达是一件特别奇怪的事情系统。

无论如何,我开始思考答案,“标准要求所有容器的值类型都是可分配的。”而且,据我所知,现在我找到了 C++03 标准草案的旧拷贝,这是真的;它做了。

另一方面,值类型 std::mapstd::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 中使用测试,根据我今天看到的情况,它看起来不太可靠。

那么有没有人可以阐明这个问题(在标题中):可分配的真正含义是什么?还有两个奖励问题:

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),然后 referenceconst_reference是相同的类型,因此声明是重复的。 (定义的主体是相同的,因为它的值(value)。)因此,没有分配器感知容器可以处理显式 const值类型。隐藏 const里面tuple允许分配器实例化。标准中的这个分配器要求用于证明至少关闭几个关于 std::vector<const int> 问题的旧 libstdc++ 错误是合理的。 ,虽然我觉得这不是一个坚实的原则。 libc++ 也以一种显而易见的简单方式解决了这个问题,即提供 allocator<const T> 的特化。删除了重复的函数声明。

最佳答案

在 C++03 中,Assignable由 §23.1/4 中的表 64 定义,

    Expression    Return type    Post-condition
    t = u         T&             t is equivalent to u

On the one hand this requirement was not met for std::map. On the other hand it was too strict a requirement for std::list. And C++11 demonstrated that it's not even necessary for std::vector, in general, but is imposed by use of certain operations (such as assignment).

In C++11 the corresponding requirement is named CopyAssignable and is defined by table 23 in §17.6.3.1/2,

    Expression    Return type    Return value    Post-condition
    t = v         T&             t               t is equivalent to v, the
                                                 value of v is unchanged

The main differences are that container elements no longer need to be CopyAssignable, and that there's a corresponding requirement MoveAssignable.

Anyway, a structure with a const data member is clearly not assignable unless one chooses to read “equivalent to” with a very peculiar interpretation.

The only operation-independent element type requirement in C++11 is, as far as I can see (from table 96 in §23.2.1/4) that it must be Destructible.


Regarding std::is_assignable, it does not quite test the CopyAssignable criterion.

Here’s what std::is_assignable<T, U> implies, according to table 49 in C++11 §20.9.4.3/3:

“The expression declval<T>() = declval<U>() is well-formed when treated as an unevaluated operand (Clause 5). Access checking is performed as if in a context unrelated to T and U. Only the validity of the immediate context of the assignment expression is considered. [Note: The compilation of the expression can result in side effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such side effects are not in the “immediate context” and can result in the program being ill-formed. —end note ]”

Essentially this implies an access/existence + argument type compatibility check of operator=, and nothing more.

However, Visual C++ 11.0 doesn't appear to do the access check, while g++ 4.7.1 chokes on it:

#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]
> classignable.cpp
可分配的.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:替换'模板静态 decltype (((declval)()=(declval)(), std::__sfinae_types::__one()))std::__is_assignable_helper::__test(int) [with _
Tp1 = _Tp1; _Up1 = _Up1; _Tp = B; _Up = B] [与 _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:从'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: 'struct std::is_assignable' 需要
assignable.cpp:10:59: 来自'bool isAssignable() [with Type = B]'
assignable.cpp:16:32: 从这里需要
assignable.cpp:7:24: 错误:'B& B::operator=(const B&)' 是私有(private)的
在 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/包含的文件中位/移动.h:57:0,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/STL_pair .h:61,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/STL_algobase .h:65,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits .h:41,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41 ,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40 ,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40 ,
来自assignable.cpp:1:
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1049: 2:错误:在此上下文中
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:替换'模板静态 decltype (((declval)()=(declval)(), std::__sfinae_types::__one())) std::__is_assignable_helper::__test(int) [with _
Tp1 = _Tp1; _Up1 = _Up1; _Tp = B; _Up = B] [与 _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:从'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: 'struct std::is_assignable' 需要
assignable.cpp:10:59: 来自'bool isAssignable() [with Type = B]'
assignable.cpp:16:32: 从这里需要
assignable.cpp:7:24: 错误:'B& B::operator=(const B&)' 是私有(private)的
在 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/包含的文件中位/移动.h:57:0,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/STL_pair .h:61,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/STL_algobase .h:65,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits .h:41,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41 ,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40 ,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40 ,
来自assignable.cpp:1:
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1049: 2:错误:在此上下文中
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:在实例化中'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: 'struct std::is_assignable' 需要
assignable.cpp:10:59: 来自'bool isAssignable() [with Type = B]'
assignable.cpp:16:32: 从这里需要
assignable.cpp:7:24: 错误:'B& B::operator=(const B&)' 是私有(private)的
在 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/包含的文件中位/移动.h:57:0,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/STL_pair .h:61,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/STL_algobase .h:65,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits .h:41,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41 ,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40 ,
来自 d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40 ,
来自assignable.cpp:1:
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055: 68:错误:在此上下文中

[D:\dev\test\so\assignable]
> _

所以,总结一下,标准的std::is_assignable似乎实用性非常有限,在撰写本文时,它不能用于可移植代码。

编辑:已替换 <utility>正确 <type_traits .有趣的是,它对 g++ 并不重要。甚至对于错误消息也没有,所以我只是让它保持原样。

关于c++ - "Assignable"到底是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14009120/

相关文章:

c++ - constexpr 和带有重新解释转换的静态 const void 指针的初始化,哪个编译器是正确的?

C++/OpenGL : Texture to pixmap example - narrowing conversion error

c++ - g++ 和 clang++ 都存在模板函数参数包扩展问题?

c++ - 为什么这个类型转换模棱两可?

c++ - 是否有粘性操纵器的官方资源

c++ - C++ 中的声明

c++ - 奇怪的错误 char 的加密 (ascii)

c++ - 将结构转换为字符数组

c++ - QPolarChart隐藏径向刻度标签

C++可以setw和setfill填充字符串的末尾吗?