I have the following code (as much simplified as possible):
#include <vector>
#include <algorithm>
using std::vector;
enum class Foo {
BAR, BAZ
};
void print_to_file(const vector<const vector<Foo> >& sequences) {
std::for_each(sequences.begin(), sequences.end(), [](const vector<Foo>& sequence) {
});
}
int main(int argc, char **argv) {
return EXIT_SUCCESS;
}
when compiling with g++ (SUSE Linux) 4.7.1 20120723 [gcc-4_7-branch revision 189773] as g++ --std=c++11 test.cpp I get the following error message:
In file included from /usr/include/c++/4.7/x86_64-suse-linux/bits/c++allocator.h:34:0,
from /usr/include/c++/4.7/bits/allocator.h:48,
from /usr/include/c++/4.7/vector:62,
from test.cpp:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘struct __gnu_cxx::new_allocator<const std::vector<Foo> >’:
/usr/include/c++/4.7/bits/allocator.h:89:11: required from ‘class std::allocator<const std::vector<Foo> >’
/usr/include/c++/4.7/bits/alloc_traits.h:89:43: required from ‘struct std::allocator_traits<std::allocator<const std::vector<Foo> > >’
/usr/include/c++/4.7/ext/alloc_traits.h:109:10: required from ‘struct __gnu_cxx::__alloc_traits<std::allocator<const std::vector<Foo> > >’
/usr/include/c++/4.7/bits/stl_vector.h:76:28: required from ‘struct std::_Vector_base<const std::vector<Foo>, std::allocator<const std::vector<Foo> > >’
/usr/include/c++/4.7/bits/stl_vector.h:208:11: required from ‘class std::vector<const std::vector<Foo> >’
test.cpp:11:25: required from here
/usr/include/c++/4.7/ext/new_allocator.h:83:7: error: ‘const _Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::const_reference) const [with _Tp = const std::vector<Foo>; __gnu_cxx::new_allocator<_Tp>::const_pointer = const std::vector<Foo>*; __gnu_cxx::new_allocator<_Tp>::const_reference = const std::vector<Foo>&]’ cannot be overloaded
/usr/include/c++/4.7/ext/new_allocator.h:79:7: error: with ‘_Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::reference) const [with _Tp = const std::vector<Foo>; __gnu_cxx::new_allocator<_Tp>::pointer = const std::vector<Foo>*; __gnu_cxx::new_allocator<_Tp>::reference = const std::vector<Foo>&]’
the same code compiles fine with VS2012, but fails badly under g++ and I've not the slightest idea how to interpret the error message.
I'm surprised you don't get problems elsewhere, as this vector<const vector<Foo> > is not a possible data type.
The value_type of a std::vector must at minimum be copy constructible (C++03) or movable (C++11). If it is const, you cannot assign or move it.
The Allocator requirements used for standard containers doesn't allow for const objects to be held by the containers. C++11 17.6.3.5 "Allocator requirements" outlines the requirements for standard allocators using a set of tables which start with Table 27. In Table 27, the first definition is for the 'descriptive variables' T, U, and C which are defined as "any non-const, non-reference object type". The descriptive variables X and Y, used for the Allocator class defintions, are:
X - an Allocator class for type T
Y - the corresponding Allocator class for type U
In summary, standard Allocators are defined in terms of parameterization by non-const types.
There's an old GCC/libstdc++ bug for this (resolved as invalid) going back to 2004 (C++2003 has a similar limitation against storing const objects in containers, but it's simpler to show because the 2003 standard more straightforwardly says that the types of objects stored in containers must be 'Assignable').
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=16875
Related
#include <iostream>
#include <unordered_map>
#include <string>
struct tree_node {
// tree_node() : attrib_val{"null"} {}
std::unordered_map<std::string, tree_node> child;
};
int main(int argc, char const *argv[])
{
return 0;
}
This code compiles just fine on my mac with clang:
$ g++ --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
$ g++ -std=c++11 test.cpp
$
On my linux machine, with gcc 9.1.0, I get the following error:
In file included from /usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_algobase.h:64,
from /usr/um/gcc-9.1.0/include/c++/9.1.0/bits/char_traits.h:39,
from /usr/um/gcc-9.1.0/include/c++/9.1.0/ios:40,
from /usr/um/gcc-9.1.0/include/c++/9.1.0/ostream:38,
from /usr/um/gcc-9.1.0/include/c++/9.1.0/iostream:39,
from test.cpp:1:
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_pair.h: In instantiation of ‘struct std::pair<const std::__cxx11::basic_string<char>, tree_node>’:
/usr/um/gcc-9.1.0/include/c++/9.1.0/ext/aligned_buffer.h:91:28: required from ‘struct __gnu_cxx::__aligned_buffer<std::pair<const std::__cxx11::basic_string<char>, tree_node> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:233:43: required from ‘struct std::__detail::_Hash_node_value_base<std::pair<const std::__cxx11::basic_string<char>, tree_node> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:264:12: required from ‘struct std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, tree_node>, true>’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:2016:13: required from ‘struct std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, tree_node>, true> > >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable.h:173:11: required from ‘class std::_Hashtable<std::__cxx11::basic_string<char>, std::pair<const std::__cxx11::basic_string<char>, tree_node>, std::allocator<std::pair<const std::__cxx11::basic_string<char>, tree_node> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char> >, std::hash<std::__cxx11::basic_string<char> >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/unordered_map.h:105:18: required from ‘class std::unordered_map<std::__cxx11::basic_string<char>, tree_node>’
test.cpp:7:46: required from here
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_pair.h:215:11: error: ‘std::pair<_T1, _T2>::second’ has incomplete type
215 | _T2 second; /// #c second is a copy of the second object
| ^~~~~~
test.cpp:5:8: note: forward declaration of ‘struct tree_node’
5 | struct tree_node {
It doesn't like the tree_node as a value in unordered_map for some reason.
This is undefined behavior, by [res.on.functions]/2.5:
[The effects are undefined if] an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.
This is an annoying case where I basically have to prove a negative for this answer to be valid, but I find no place in the standard that mentions an exception that allows you to use an incomplete type in a std::map. Therefore, this program can do anything. In particular, though Clang compiles it now, it may stop working at any point in the future, and there's also a chance that the compiled code map specialization doesn't work properly. Certain containers, especially std::vector, have a clause that allows them to be instantiated at incomplete types under the right conditions. But this case is undefined behavior, and so compilers have no obligation to warn you or error. Change your program somehow to avoid this. I believe the following would be legal, without forcing you to store too many extra pointers.
struct tree_node {
std::unique_ptr<std::unordered_map<std::string, tree_node>> child;
};
std::unique_ptr is an exception to the general no-go rule—it's OK to instantiate it at an incomplete type (but some of its members aren't as lax). I believe that this means that std::unordered_map<std::string, tree_node> is not required to be complete at the point in the definition of tree_node where the specialization of std::unique_ptr is required to be complete, and so the std::unordered_map specialization is not triggered and UB is avoided since tree_node is not required to be complete. Note that you can still write constructors, functions, a destructor, etc. without worry, since all of those definitions are implicitly moved out of and after the class definition, and tree_node becomes complete after the class definition ends.
Changing:
struct tree_node {
// tree_node() : attrib_val{"null"} {}
std::unordered_map<std::string, tree_node> child;
};
To:
struct tree_node {
// tree_node() : attrib_val{"null"} {}
std::unordered_map<std::string, tree_node *> child;
};
Makes the compilation problem go away. Whether it has any reflection on what you wanted?
i have to continue a programm. The programmer before me used the structure a lot:
std:vector< T* const>
He wrote ist in Visual Studio C++ 2010 and was able to compile this. I am using g++ and it thwrows some compilation errors.
g++ -g -Wall -c -std=c++11 -pedantic -I/usr/include/SuperLU/ src/Cell.cpp -o obj/Cell.o
In file included from src/Cell.cpp:13:0:
src/Cell.h:81:2: warning: extra ';' [-pedantic]
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/bits/locale_classes.h:42,
from /usr/include/c++/4.7/bits/ios_base.h:43,
from /usr/include/c++/4.7/ios:43,
from /usr/include/c++/4.7/ostream:40,
from /usr/include/c++/4.7/iostream:40,
from src/Cell.cpp:2:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of 'struct __gnu_cxx::new_allocator<BattPackage::Leg* const>':
/usr/include/c++/4.7/bits/allocator.h:89:11: required from 'class std::allocator<BattPackage::Leg* const>'
/usr/include/c++/4.7/bits/alloc_traits.h:92:43: required from 'struct std::allocator_traits<std::allocator<BattPackage::Leg* const> >'
/usr/include/c++/4.7/ext/alloc_traits.h:110:10: required from 'struct __gnu_cxx::__alloc_traits<std::allocator<BattPackage::Leg* const> >'
/usr/include/c++/4.7/bits/stl_vector.h:76:28: required from 'struct std::_Vector_base<BattPackage::Leg* const, std::allocator<BattPackage::Leg* const> >'
/usr/include/c++/4.7/bits/stl_vector.h:208:11: required from 'class std::vector<BattPackage::Leg* const>'
src/Cell.h:283:39: required from here
/usr/include/c++/4.7/ext/new_allocator.h:83:7: error: 'const _Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::const_reference) const [with _Tp = BattPackage::Leg* const; __gnu_cxx::new_allocator<_Tp>::const_pointer = BattPackage::Leg* const*; __gnu_cxx::new_allocator<_Tp>::const_reference = BattPackage::Leg* const&]' cannot be overloaded
/usr/include/c++/4.7/ext/new_allocator.h:79:7: error: with '_Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::reference) const [with _Tp = BattPackage::Leg* const; __gnu_cxx::new_allocator<_Tp>::pointer = BattPackage::Leg* const*; __gnu_cxx::new_allocator<_Tp>::reference = BattPackage::Leg* const&]
What he wanted with this structure was a vector of pointers, where he can add and remove the pointers and manipulate the target of the pointers but not change on which object the pointer points.
As far as I understand it should not compile because T* const is not assignable.
Does anyone know why it compiles in Visual Studio ?
And can I replace that in the declarations without the need to change the complete code?
std::vector uses an Allocator to allocate, reallocate, and deallocate memory for its members. The Allocator requirements are only defined for an Allocator X for type T, where T is "any non-const, non-reference object type" (C++11 Table 27). So using a std::vector<T* const> gives you undefined behaviour, because the allocator it uses is not defined for a const element type.
You're better off fixing this as soon as possible. There are unlikely to be many issues with changing the declarations to std::vector<T*>.
A member is defined as
std::shared_ptr<std::array<std::string, 6> > exit_to;
which points to additional data shared among others.
When try to initiate the pointer "exit_to". The correct way is
node_knot.exit_to = std::make_shared<std::array<std::string, 6> >();
But it's in another file and I'd like to keep the pointer type consistent, something like this:
node_knot.exit_to = std::make_shared<decltype(*node_knot.exit_to)>();
But won't compile:
/usr/include/c++/4.6/bits/shared_ptr_base.h:798:54: error: '__p'
declared as a pointer to a reference of type
'std::array<std::basic_string<char>, 6> &'
__shared_ptr(const __shared_ptr<_Tp1, _Lp>& __r, _Tp* __p)
^ /usr/include/c++/4.6/bits/shared_ptr.h:93:31: note: in instantiation
of template class
'std::__shared_ptr<std::array<std::basic_string<char>, 6> &, 1>'
requested here
class shared_ptr : public __shared_ptr<_Tp>
^ ../node_booker.h:757:20: note: in
instantiation of template class
'std::shared_ptr<std::array<std::basic_string<char>, 6> &>' requested
here
n.exit_to = std::make_shared<decltype(*n.exit_to)>();
I'm under Ubuntu 12.10, clang++ 3.2, with --std=c++11
You need to remove the reference from the type you are passing to make_shared.
The following should work:
node_knot.exit_to = std::make_shared<std::remove_reference<decltype(*node_knot.exit_to)>::type>();
The problem is that the type of *exit_to is a reference, and you can't have a shared_ptr to a reference.
You could remove the reference, but instead of finding the type returned by operator* and then stripping the reference off it, it's probably easier to just ask the shared_ptr what type it contains:
node_knot.exit_to = std::make_shared<decltype(node_knot.exit_to)::element_type>();
The nested element_type is the type stored by the shared_ptr.
Another option would be to add a typedef to the class and use it consistently wherever you need it:
typedef std::array<std::string, 6> string_array;
std::shared_ptr<string_array> exit_to;
// ...
node_knot.exit_to = std::make_shared<Node::string_array>();
This is a lot more readable than using decltype
C++11 removed the requirement that the value type of all containers be CopyConstructible and Assignable (although specific operations on containers may impose these requirements). In theory, that should make it possible to define, for example, std::deque<const Foo>, which was not possible in C++03.
Unexpectedly, gcc 4.7.2 produced its usual vomit of incomprehensible errors [1] when I tried this, but clang at least made the error readable and clang with libc++ compiled it with no errors.
Now, when two different compilers produce different results, it always makes me wonder what the correct answer is, and so I searched out all the references I could find to const/assignable/value types/containers, etc., etc. I found almost a decade's worth of very similar questions and answers, some of them here on SO and others in the various C++ mailing lists, amongst other places, including the Gnu buganizer, all of which basically can be summarized as following dialogue.
Q: Why can't I declare std::vector<const int> (as a simplified example)
A: Why on earth would you want to do that? It's nonsensical.
Q: Well, it makes sense to me. Why can't I do it?
A: Because the Standard requires value types to be assignable.
Q: But I'm not planning on assigning them. I want them to be const after I've created them.
A: That's not the way it works. Next question!
with a mild dashing of:
A2: C++11 has decided to allow that. You'll just have to wait. In the meantime, rethink your ridiculous design.
These don't seem like very compelling answers, although possibly I'm biased because I fall into the category of "but it makes sense to me". In my case, I'd like to have a stack-like container in which things pushed onto the stack are immutable until they are popped, which doesn't strike me as a particularly odd thing to want to be able to express with a type system.
Anyway, I started thinking about the answer, "the standard requires all containers' value types to be assignable." And, as far as I can see, now that I found an old copy of a draft of the C++03 standard, that's true; it did.
On the other hand, the value type of std::map is std::pair<const Key, T> which doesn't look to me like it's assignable. Still, I tried again with std::deque<std::tuple<const Foo>>, and gcc proceeded to compile it without batting an eye. So at least I have some kind of workaround.
Then I tried printing out the value of std::is_assignable<const Foo, const Foo> and std::is_assignable<std::tuple<const Foo>, const std::tuple<const Foo>>, and it turns out that the former is reported as not assignable, as you'd expect, but the latter is reported as assignable (by both clang and gcc). Of course, it's not really assignable; attempting to compile a = b; is rejected by gcc with the complaint error: assignment of read-only location (this was just about the only error message I encountered in this quest which was actually easy to understand). However, without the attempt to do an assignment, both clang and gcc are equally happy to instantiate the deque<const>, and the code seems to run fine.
Now, if std::tuple<const int> really is assignable, then I can't complain about the inconsistency in the C++03 standard -- and, really, who cares -- but I find it disturbing that two different standard library implementations report that a type is assignable when in fact, attempting to assign to a reference of it will lead to a (very sensible) compiler error. I might at some point want to use the test in a template SFINAE, and based on what I saw today, it doesn't look very reliable.
So is there anyone who can shed some light on the question (in the title): What does Assignable really mean? And two bonus questions:
1) Did the committee really mean to allow instantiating containers with const value types, or did they have some other non-assignable case in mind?, and
2) Is there really a significant difference between the constnesses of const Foo and std::tuple<const Foo>?
[1] For the truly curious, here's the error message produced by gcc when trying to compile the declaration of std::deque<const std::string> (with a few line-endings added, and an explanation if you scroll down far enough):
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>&]’
So what's going on here is that the standard (§ 20.6.9.1) insists that the default allocator have member functions:
pointer address(reference x) const noexcept;
const_pointer address(const_reference x) const noexcept;
but if you instantiate it with a const template argument (which is apparently UB), then reference and const_reference are the same type, and so the declarations are duplicated. (The body of the definition is identical, for what it's worth.) Consequently, no allocator-aware container can deal with an explicitly const value type. Hiding the const inside a tuple allows the allocator to instantiate. This allocator requirement from the standard was used to justify closing at least a couple of old libstdc++ bugs about problems with std::vector<const int>, although it doesn't strike me as a solid point of principle. Also libc++ works around the problem in the obvious simple way, which is to provide a specialization of allocator<const T> with the duplicate function declarations removed.
In C++03, Assignable was defined by table 64 in §23.1/4,
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.
}
Building with Visual C++ 11.0:
[D:\dev\test\so\assignable]
> cl assignable.cpp
assignable.cpp
[D:\dev\test\so\assignable]
> _
Building with 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]
> _
So, summing up, the standard's std::is_assignable appears to be of very limited utility, and as of this writing it can't be relied on for portable code.
EDIT: Replaced <utility> with correct <type_traits. Interestingly it didn't matter for g++. Not even for the error message, so I just let that be as it was.
I'm giving this to Alf but I wanted to add a couple of notes for future reference.
As Alf says, std::is_*_assignable really only check for the existence (explicit or implicit) of an appropriate assignment operator. They don't necessarily check to see whether it will be well-formed if instantiated. This works fine for the default assignment operators. If there is a member declared const, the default assignment operators will be deleted. If a base class has a deleted assignment operator, the default assignment operator will be deleted. So if you just let the defaults do their thing, it should be fine.
However, if you do declare operator=, it becomes your responsibility (if you care) to ensure that it is appropriately deleted. For example, this will compile and run (at least with clang), and reports that 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;
}
The absence of the assignment operator's definition isn't noted until link-time, and in this case not at all because the assignment operator is never used. But note that a use of C::operator= contingent on std::is_assignable would be allowed to compile. Of course, I couldn't have defined C::operator= in a way that resulted in assigning to its member a, because that member isn't assignable.
That's not a particularly interesting example though. What get's interesting is the use of templates, such as the std::tuple issue which started this whole question. Let's add a couple of templates into the above, and actually define C::operator= through assignment to its member 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;
}
Without the assignment at the end, the code compiles and runs (under clang 3.3) and reports that A<false> is not assignable (correct) but that C<false> is assignable (surprise!). The actual attempt to use C::operator= reveals the truth, because it is not until that point that the compiler attempts to actually instantiate that operator. Up to then, and through the instantiations of is_assignable, the operator was just a declaration of a public interface, which is -- as Alf says -- all that std::is_assignable is really looking for.
Phew.
So bottom line, I think this is a deficiency in both the standard and the standard library implementations with respect to standard aggregate objects whose operator= should be deleted if any of the component types is not assignable. For std::tuple, § 20.4.2.2 lists as requirements for operator= that all component types be assignable, and there ae similar requirements for other types, but I don't think that requirement requires library implementors to delete inapplicable operator=.
But, then, as far as I can see, nothing stops library implementations from doing the deletion (except the annoyance factor of conditionally deleting assignment operators). In my opinion after obsessing about this for a couple of days, they should do so, and furthermore the standard should require them to do so. Otherwise, reliable use of is_assignable impossible.
I'm using the latest available GCC build from repository. I decided to use it because some additional C++0x features. However now I stuck with something what supposes to work - I want to add new element to map via r-value. Simplified code, which demonstrates problem:
#include <tr1/unordered_map>
class X
{
public:
X (void) { /* ... */ };
X (const X& x) = delete;
X (X&& x) { /* ... */ };
};
int main (void)
{
std::tr1::unordered_map<int, X> map;
// using std::tr1::unordered_map<int, X>::value_type didn't help too
std::pair<int, X> value (1, X ());
map.insert (std::move (value));
}
Notice, that when replace X class with some primitive type like int code compiles and work just fine.
In my production code class corresponding to X doesn't have copy constructor too.
Error message is (like all template-related errors) long and unreadable and I'm not sure if it's good idea to put it here. Notify me if you want error message, so I'll update this question. Last part of message is interesting:
(...)
/usr/include/c++/trunk/ext/new_allocator.h:106:9: error: use of deleted function ‘constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = const int, _T2 = X, std::pair<_T1, _T2> = std::pair<const int, X>]’
In file included from /usr/include/c++/trunk/utility:71:0,
from /usr/include/c++/trunk/tr1/unordered_map:34,
from kod.cpp:1:
/usr/include/c++/trunk/bits/stl_pair.h:110:17: error: ‘constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = const int, _T2 = X, std::pair<_T1, _T2> = std::pair<const int, X>]’ is implicitly deleted because the default definition would be ill-formed:
/usr/include/c++/trunk/bits/stl_pair.h:110:17: error: use of deleted function ‘X::X(const X&)’
Moreover this should work, because similar bug was already fixed [C++0x] Implement emplace* in associative and unordered containers.
Maybe I'm doing something wrong? I want to be sure, that's GCC or libstdc++ bug before reporting it.
Your code looks correct to me except for your use of tr1. tr1-qualified stuff doesn't know about rvalue reference or move semantics.
I took your code, removed tr1 from the header and namespace qualifiers, and successfully compiled your code using g++-4.4 and libc++ (http://libcxx.llvm.org/). Try removing tr1.
The value_type of that unordered_map is not std::pair<int, X>. It is std::pair<const int, X>. Maybe if you use that type for the value it will work better.
decltype(map)::value_type value(1, X());
map.insert(std::move(value));
Although I don't exactly see why your code shouldn't work as is.