Why does [std::is_move_assignable] not behave as expected? - c++

#include <unordered_map>
#include <type_traits>
int main()
{
std::unordered_multimap<int, string> m{ { 1, "hello" } };
auto b = std::is_move_assignable_v<decltype(*m.begin())>;
// b is true
auto v = *m4.begin(); // ok
v = std::move(*m4.begin()); // compile-time error
}
Issue:
If b is true, then v = *m4.begin(); should be ok.
Question:
Why does std::is_move_assignable not behave as expected?
Error messages: (Clang 3.8 + Visual Studio 2015 Update 3)
error : cannot assign to non-static data member 'first' with
const-qualified type 'const int'
first = _Right.first;
~~~~~ ^
main.cpp(119,5) : note: in instantiation of member function
'std::pair<const int, std::basic_string<char, std::char_traits<char>,
std::allocator<char> > >::operator=' requested here
v = *m4.begin(); // error

If b is true, then v = *m4.begin(); should be ok.
v = *m4.begin(); is copy assignment, not move assignment. Move assignment is v = std::move(*m4.begin());.
But as T.C. points out and Yam Marcovic answered as well, your use of std::is_move_assignable is wrong, because decltype(*m.begin()) is a reference type. You end up not quite checking for move assignability, and your check does end up right for v = *m4.begin();.
For move assignment, the check should be std::is_move_assignable_v<std::remove_reference_t<decltype(*m.begin())>>.
Anyway, is_move_assignable doesn't try too hard to check whether the type is move assignable, it only checks whether the type reports itself as move assignable.
Given
template <typename T>
struct S {
S &operator=(S &&) { T() = "Hello"; return *this; }
};
std::is_move_assignable<S<int>>::value will report true, because the signature S<int> S::operator=(S<int> &&); is well-formed. Attempting to actually call it will trigger an error, since T() cannot be assigned to, and even if it could be, it couldn't be assigned a string.
In your case, the type is std::pair<const int, std::string>. Note the const there.
std::pair<T1, T2> currently always declares operator=, but if either T1 or T2 cannot be assigned to, attempting to actually use it will produce an error.
Class templates may make sure that a valid signature of operator= is only declared if its instantiation would be well-formed to avoid this problem, and as also pointed out by T.C., std::pair will do so in the future.

First, as *begin() returns an lvalue reference, you're actually passing an lvalue reference to std::is_move_assignable, which is then implemented by means of std::is_assignable<T&, T&&>. Now, notice the forwarding reference there, which converts to an lvalue reference. This means that by asking std::is_move_assignable<SomeType&> you have effectively asked std::is_assignable<T&, T&>—i.e. copy-assignable. Granted, this is a bit misleading.
You might want to test this code to see my point:
#include <iostream>
#include <type_traits>
struct S {
S& operator=(const S&) = default;
S& operator=(S&&) = delete;
};
int main()
{
std::cout << std::is_move_assignable<S>::value; // 0
std::cout << std::is_move_assignable<S&>::value; // 1
}
All that said, *begin() in this case returns a pair<const int, string>&, so it would not be any-kind-of-assignable anyhow, because you cannot assign to a const int. However, as #hvd pointed out, since the function is declared in principal in the pair template, the type trait does not understand that if it were ever generated into an actual function by the compiler, the latter would encounter an error.

Dereferencing .begin() iterator of empty container is undefined behaviour.

Related

Why do I need to move `std::unique_ptr`

Given the following code:
#include <iostream>
#include <memory>
struct A {};
struct B : public A {};
std::pair<bool, std::unique_ptr<B>> GetBoolAndB() {
return { true, std::make_unique<B>() };
}
std::unique_ptr<A> GetA1() {
auto[a, b] = GetBoolAndB();
return b;
}
std::unique_ptr<A> GetA2() {
auto [a, b] = GetBoolAndB();
return std::move(b);
}
GetA1 does not compile, with this error:
C2440: 'return': cannot convert from 'std::unique_ptr<B,std::default_delete<_Ty>>' to 'std::unique_ptr<A,std::default_delete<_Ty>>'
while GetA2 does compile without errors.
I don't understand why I need to call std::move to make the function work.
Edit
Just to clarify, as pointed out in comments by DanielLangr, my doubt was about the fact that
std::unique_ptr<A> GetA3() {
std::unique_ptr<B> b2;
return b2;
}
compiles and transfer ownership without the need for std::move.
Now I understand that in case of GetA1 and GetA2, with structured bindings it happens that b is part of some object, and so it must be moved to become an rvalue reference.
I don't understand why I need to call std::move to make the function work.
Because the corresponding constructor of std::unique_ptr has a parameter of rvalue reference type:
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;
See documentation for details: https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr
Since rvalue references cannot bind lvalues, consequently, you cannot use b (which is lvalue) as an argument of this constructor.
If you wonder why b is treated as lvalue in the return statement, see, for example: Why Structured Bindings disable both RVO and move on return statement? In short, b is not a variable with automatic storage duration, but a reference to a pair element instead.
The error message basically just says that the compiler could not find any viable converting constructor, therefore, it "cannot convert...".
By wrapping b with std::move call, you are creating an expression that refers to the very same object as b, but its category is rvalue. Which may be bound with that constructor parameter.
Because there should only be one valid unique_ptr at any one time.
That is why it is called unique_ptr.
unique_ptr is un-copyable, you must move it.
Otherwise you would end up with a copy of the pointer which would defeat the point of it being unique!
See: Rules for Smart Pointers

c++ no operator matches even when operator is defined

I am trying to implement custom reference in C++. What I want to achieve is to have reference, which does not need to be set at creation. It looks like this
template<typename T>
class myreference
{
public:
myreference() : data(), isset(false) { }
explicit myreference(const T& _data) : data(&_data), isset(true) { }
myreference<T>& operator=(T& t)
{
if (!isset)
{
isset= true;
data = &t;
}
else
*data = t;
return *this;
}
operator T() const { return *data; }
operator T&() { return *data; }
private:
T* data;
bool isset;
};
It works fine. I can do this, except for last statement.
myreference<int> myref;
int data = 7, test = 3;
myref = data; // reference is set
myref = test; // data is now 3
int& i = myref;
i = 4; // data is now 4
cout << myref; // implicit int conversion
myref = 42; // error C2679: binary '=' : no operator found which takes a right-hand operand of type 'int'
Full error
error C2679: binary '=' : no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion)
1> d:\...\main.cpp(33): could be 'myreference<int> &myreference<int>::operator =(const myreference<int> &)'
1> d:\...\main.cpp(16): or 'myreference<int> &myreference<int>::operator =(T &)'
1> with
1> [
1> T=int
1> ]
1> while trying to match the argument list 'myreference<int>, int'
I was searching through the web and found similar errors (with different operators) and the result was to put the operator outside of its class, but that is not possible for operator= for some (I believe good) reason. My question is, what is the compiler complaining about? Argument list is myreference<int>, int and I have operator defined for Ts which, in this case, is int.
You instantiated an object of type myreference<int>
myreference<int> myref;
So the assignment operator is specialized for parameter type int &. However you are trying to assogn a temporary object that may be bound to a constant reference. So the assignment operator has to be specialized for parameter type const int &.
Thus you need to define another object of type myreference<const int> that the code would be at least compiled. For example
myreference<const int> myref1;
myref1 = 42;
However in any case the code will have undefined behaviour because the temporary object will be deleted after the executing this assignment statement and data member data will have invalid value.
Usually such claases suppress using temporary objects that to avoid the undefined behaviour.
Your problem is bigger than the compiler error. The compiler error is telling you that you have a fundamental design error.
Your reference = both acts as a reference rebinder (changes what an empty reference is attached to) and an assignment (chanhes the value of the thing attached to). These are fundamantally different operations.
One requires a long-term value you can modify later (rebind), and the other requres a value you can read from (assignment).
However with one function, the parameter passed has to be either -- and those types do not match. A readable from value is a T const&. A bindable value is a T&.
If you change the type to T const& the rebind data = &t fails, as it should. If you pass a temporary, like 42, to your =, a rebind attaches to it, and becomes invalid at the end of the calling line. Similarly if you assign int const foo = 3; to it.
You can 'fix' this with a const_cast, but that just throws the type checking out the window: it hides the undefined behaviour instead of giving you a diagnosis.
The T& parameter, on the other hand, cannot bind to a 42 temporary, nor can it bind to a constant const int foo = 3. Which is great in the rebind case, but makes assignment sort of useless.
If you have two operations, =(T const &) and .rebind(T&), your problem goes away. As a side effect, = when not set is indefined behaviour. But that was basically true before.
Your reference is probably better called a 'pointer' at this point.
myreference<T>& operator=(T& t)
myref = 42;
non-const reference can't be bound to a temporary. You can change it to take parameter by const reference.
myreference<T>& operator=(const T& t)
Also, make changes in your code to make this work.
data = &t; <<< Replace this
data = const_cast<T*>(&t); <<< with this.
Since assignment operator is expecting variable (in this case an integer) you are not allowed to pass it some value(42).
So use an integer like
int x=42;
myref=x;
Happy coding.

why cast a smart pointer of const type to smart pointer of type works

Any idea why a1 =a2 does not work but a2=a1 works. There must be a function in the smart pointer template that does the conversion? which one is it?
#include "stdafx.h"
#include<memory>
class TestClass
{
public:
int a ;
};
typedef std::shared_ptr<TestClass> TestSP;
typedef std::shared_ptr<const TestClass> TestConstSP;
int _tmain(int argc, _TCHAR* argv[])
{
TestSP a1 = TestSP();
TestConstSP a2 = TestConstSP();
//a1 =a2; //error C2440: '<function-style-cast>' : cannot convert from 'const std::shared_ptr<_Ty>' to 'std::shared_ptr<_Ty>'
a2=a1;
return 0;
}
This is due to the usage of const. Should you have a const pointer 'const_ptr' and a non const pointer 'non_const_ptr', it is OK to do:
const_ptr = non_const_ptr; // const_ptr doesn't allow modifying the pointed value, while non_const_ptr does.
But it is forbidden to do:
non_const_ptr = const_ptr; // const_ptr is `const`. Allowing non_const_ptr to modify the pointed value would'n respect the `const` contract.
And the following would work:
non_const_ptr = (type *) const_ptr; // You have the right to do that as you explicitely break the contract.
// The programmer is the boss. This is a bit ugly though.
The exact same logic applies to your example.
This is specified in an interesting way, and it looks like MSVC implemented the standard to the letter here. The assignment itself is specified in §20.8.2.2.3 [util.smartptr.shared.assign]/p1-3:
shared_ptr& operator=(const shared_ptr& r) noexcept;
template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;
template<class Y> shared_ptr& operator=(auto_ptr<Y>&& r);
Effects: Equivalent to shared_ptr(r).swap(*this).
Returns: *this.
[ Note: The use count updates caused by the temporary object construction and destruction are not
observable side effects, so the implementation may meet the effects (and the implied guarantees) via
different means, without creating a temporary. <example omitted>]
The relevant constructor is specified in §20.8.2.2.1 [util.smartptr.shared.const]/p17-19:
shared_ptr(const shared_ptr& r) noexcept;
template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
Requires: The second constructor shall not participate in the overload resolution unless Y* is implicitly convertible to T*.
Effects: If r is empty, constructs an empty shared_ptr object; otherwise, constructs a shared_ptr object that shares ownership with
r.
Postconditions: get() == r.get() && use_count() == r.use_count().
Since const TestClass * is not implicitly convertible to TestClass *, the templated constructor does not participate in overload resolution, rendering shared_ptr(r) ill-formed as there is no matching constructor.
Edit: I see the confusion. VS2012 is rather poorly designed in reporting compiler error messages. The full error message emitted by the compiler is:
error C2440: '<function-style-cast>' : cannot convert from 'const std::shared_ptr<_Ty>' to 'std::shared_ptr<_Ty>'
with
[
_Ty=const TestClass
]
and
[
_Ty=TestClass
]
Importantly, the two _Tys in the error output are referring to different types. The Error List window in VS2012, however, truncates this to the first line only, and loses that essential information. You should look at the build output for the full error message.

Why does initializing non-const reference by rvalue work (in C++11)?

The following example code compiles.
#define USE_RVALUE // line 1
template<class data_type>
class Container
{
data_type& data;
public:
#ifdef USE_RVALUE
Container(data_type&& _data) : data(_data) {}
#endif
Container(data_type& _data) : data(_data) {}
};
int main()
{
double d = 42.0;
Container<double> c1(d);
Container<double> c2(1.0f); // line 18
return 0;
}
My compiler command:
g++ -std=c++11 -Wall ref.cpp -o ref # g++ is 4.7.1
If we outcomment line 1, g++ complains:
no matching function for call to ‘Container<double>::Container(float)’
ref.cpp:18:34: note: candidates are:
ref.cpp:11:9: note: Container<data_type>::Container(data_type&) [with data_type = double]
ref.cpp:11:9: note: no known conversion for argument 1 from ‘float’ to ‘double&’
[... (more candidates)]
Of course, the error is clear, and a typical error in C++03: References from rvalues are not allowed, if these rvalues are not const. But why does it work with the rvalue constructor (i.e. the #ifdef enabled)? We have the same situation in the initializer list: Reference from non-const value.
Also, if you explain it... Is this code "good coding style" or "to avoid"?
The answer is simple enough: whatever has name - it is an lvalue. So in your ctor, with rvalue reference, _data is an lvalue in the body of ctor despite the fact it is an rvalue in the outer world. So at the end you have a dangling reference so you should not do such a thing.
Basically, when you accept something by &&(except when in template, aka Universal references) you should treat it as "this value are going to be destroyed". So you should steal from it, change it, whatever(except leaving it in some invalid state preventing its normal destruction). But you should never store pointer or reference to it. Because this object maybe destroyed right after your function complete. It is exactly the case you have. double(1.0f) exists in your ctor body only and has no meaning outside it and you still store reference to it.
It's because named r-values are not r-values.
Look at this:
double& ref = 5; // Error
const double& cref = 5; // Ok
double&& ref2 = 5; // Ok
double& ref3 = ref2; // Ok. ref2 is not r-value.
Assigning ref2 to ref3 is ok because ref2 is a named r-value.
In the same way, in your ctor _data is not an r-value, but an l-value.
Container(data_type&& _data) : data(_data) {}
When declaring parameters as taking an r-value you make it a requirement to pass an r-value to the function, but the argument itself becomes a named reference and is thus converted into an l-value. If you want to pass it to another function as an r-value you have to std::move it.

What does "Assignable" really mean?

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.