Priority queue comparator on object with const member - c++

I'm trying to implement a priority queue which uses an object which has a const member that is used to define the priority of objects in the queue. The following is a stripped down version of what I'm using
#include <vector>
#include <queue>
class Event {
public:
Event(float _time) : time(_time) {};
const float time;
};
struct EventComp {
public:
bool operator()(const Event& a, const Event& b) const {
return a.time < b.time;
}
};
class EventQueue {
private:
std::priority_queue<Event, std::vector<Event>, EventComp> events;
};
int main(int argc, char *argv[])
{
EventQueue q;
}
When I try to compile (using g++ -std=c++11), I get the following error messages, which I don't quite understand.
g++ -std=c++11 events.cpp -o events
In file included from /usr/include/c++/4.8/queue:62:0,
from events.cpp:2:
/usr/include/c++/4.8/bits/stl_heap.h: In instantiation of ‘void std::__adjust_heap(_RandomAccessIterator, _Distance, _Distance, _Tp, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<Event*, std::vector<Event> >; _Distance = long int; _Tp = Event; _Compare = EventComp]’:
/usr/include/c++/4.8/bits/stl_heap.h:448:15: required from ‘void std::make_heap(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<Event*, std::vector<Event> >; _Compare = EventComp]’
/usr/include/c++/4.8/bits/stl_queue.h:411:48: required from ‘std::priority_queue<_Tp, _Sequence, _Compare>::priority_queue(const _Compare&, _Sequence&&) [with _Tp = Event; _Sequence = std::vector<Event>; _Compare = EventComp]’
events.cpp:15:7: required from here
/usr/include/c++/4.8/bits/stl_heap.h:315:29: error: use of deleted function ‘Event& Event::operator=(Event&&)’
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));
^
events.cpp:4:7: note: ‘Event& Event::operator=(Event&&)’ is implicitly deleted because the default definition would be ill-formed:
class Event {
^
events.cpp:4:7: error: non-static const member ‘const float Event::time’, can’t use default assignment operator
In file included from /usr/include/c++/4.8/queue:62:0,
from events.cpp:2:
/usr/include/c++/4.8/bits/stl_heap.h:321:29: error: use of deleted function ‘Event& Event::operator=(Event&&)’
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first
^
I assume that some part of the internal structure of the priority_queue needs the move assignment operator to insert elements, but the const member seems to prevent that operator from being defined. I tried using the rule of five, but it didn't seem to work out. What do I need to add to the class definitions to get this working?
edit: I'm aware that I can remove the const qualifier from the member variable, make it private, and add an accessor member function to get the value of the variable, but I would rather have it public and const, so I'm interested in solutions which keep the member variable as it is in the example (if that is even possible).

The default constructor of std::priority_queue<Event, ...> requires Event to be MoveAssignable because it calls std::make_heap (which has this requirement). Event is not MoveAssignable because of the const float time data member. If you remove the const qualifier from this data member, your code should compile.
I'm aware that I can remove the const qualifier from the member
variable, make it private, and add an accessor member function to
get the value of the variable, but I would rather have it public and
const, so I'm interested in solutions which keep the member variable
as it is in the example (if that is even possible).
Ultimately, to use this std::lib facility, Event will have to be MoveAssignable. This requirement can be met with either a move assignment operator, or a copy assignment operator. Either or both of these can be supplied by you, or the compiler. But when you have a const data member, the compiler supplied special member will be implicitly deleted.
Go ahead and supply your own copy and/or move assignment operator if you desire:
class Event {
public:
Event(float _time) : time(_time) {};
const float time;
Event(Event&&) = default;
Event& operator=(Event&&); // Supply this
};
I do not have a good guess as to what you might want the move (or copy) assignment operator to do, since you have stated that once constructed, you do not want time to ever change. That is a design decision which you must work out (and has more to do with software design in general, than C++ syntax).
<aside>
In the comments of the question POW notes that the code in the question compiles with clang (more accurately, with libc++). The reason for this is a nearly conforming extension in libc++:
The standard specifies this priority_queue constructor:
explicit priority_queue(const Compare& x = Compare(),
Container&& c = Container());
which calls std::make_heap with the container c. But the standard also gives leeway to implementors to replace member function signatures with default values, with equivalent overloads. libc++ replaces this single signature with the following three:
priority_queue();
explicit priority_queue(const Compare& x);
explicit priority_queue(const Compare& x, Container&& c);
And only the 3rd needs to call std::make_heap. Thus the default constructor in libc++ places fewer requirements on the value_type.
I said nearly conforming. The part that is not completely conforming is that the default constructor is not declared explicit. This was a conscious design decision on the part of the implementor to make priority_queue easier to use.
</aside>

Related

vector::emplace_back() fills in non-sense arguments when used with pmr & default ctor

The following code tries to emplace_back a default-initialized profile class object into a pmr vector. As vector is initialized with the allocator, it enriches the childs constructor with its own allocator argument. However, there's a second argument which is added and this is why I get a huge error:
Demo
#include <memory_resource>
#include <cstdio>
struct profile
{
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
profile(allocator_type allocator = {}) {}
};
struct update
{
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
update(allocator_type allocator = {})
: profiles_( allocator )
{
}
std::pmr::vector<profile> profiles_;
};
int main() {
update u;
u.profiles_.emplace_back();
}
For full error message check out above demo. The key part seems to be this:
stl_construct.h: In substitution of 'template<class _Tp, class ... _Args> constexpr decltype (::new(void*(0)) _Tp) std::construct_at(_Tp*, _Args&& ...) [with _Tp = profile; _Args = {profile, const std::pmr::polymorphic_allocator<profile>&}]':
You can see that an argument of type profile is added additionally to the construct_at method, which is why the constructor of profile receives two arguments instead of just one (the allocator). Why is that and how can I prevent this from happening?
If your type advertises that it supports uses-allocator construction, then all of the constructors should support it.
In your case there are only implicit move and copy constructors which do not support it. The former is used by std::vector's modifiers, e.g. by emplace_back in case a reallocation and move of elements becomes necessary. The profile you are complaining about corresponds to the argument to such a move-insertion.
So add overloads
profile(profile&&, allocator_type) noexcept;
//or
profile(std::allocator_arg_t, allocator_type, profile&&) noexcept;
and
profile(const profile&, allocator_type);
//or
profile(std::allocator_arg_t, allocator_type, const profile&) noexcept;
or equivalent overloads. (Using the std::allocator_arg_t variant is probably a safer choice, so I would recommend you use it for your default constructors as well.)

How can I initialize a map without explicitly invoking an explicit constructor?

I am trying to initialize a std::map with an initializer list (in production, this is a member initializer of a class, but my minimal failing example is below). Given
#include <map>
struct Cfg {};
struct Alg
{
explicit Alg(Cfg const&) {}
};
using MyMap = std::map<int, Alg>;
int main()
{
Cfg cfg;
MyMap m = {
{1, {cfg}}, // error #1
{2, cfg}, // error #2
{3, Alg(cfg)}, // works fine
};
return 0;
}
When compiling, error #1 is:
foo.cc: In function ‘int main()’:
foo.cc:22:5: error: converting to ‘const Alg’ from initializer list would use
explicit constructor ‘Alg::Alg(const Cfg&)’
};
^
This is pretty straightforward. Passing a Cfg to the initializer requires conversion and the explicit constructor prohibits it.
Error #2 is
foo.cc: In function ‘int main()’:
foo.cc:22:5: error: converting to ‘std::pair<const int, Alg>’ from initializer list would use explicit constructor ‘constexpr std::pair<_T1, _T2>::pair(_U1&&, _U2&&) [with _U1 = int; _U2 = Cfg&; typename std::enable_if<(std::_PCC<true, _T1, _T2>::_MoveConstructiblePair<_U1, _U2>() && (! std::_PCC<true, _T1, _T2>::_ImplicitlyMoveConvertiblePair<_U1, _U2>())), bool>::type <anonymous> = 0; _T1 = const int; _T2 = Alg]’
};
^
This is a little more confusing. I think the error talks about implicitly invoking an explicit std::pair constructor; reading the documentation for std::pair, I get lost in which constructors are explicit when.
The third case is explicit construction. For reasons of maintainability, I'd rather not do that.
It seems like I've read that many initializer list counter-intuitive issues are solvable by adding more braces (gross simplification) but I confess I'm lost at this point.
If I remove the explicit qualification of the Alg constructor, all three cases compile. However, I'm not sure it makes sense to provide implicit conversion just to simplify an initializer list.
Is there a way to initialize my map elements without explicitly constructing the Alg members? Using g++ 7.3
As far as I know, there is no way around specifying the Alg type. This is the intention of explicit constructors anyhow. Just for the sake of [I don't know what to say here], you can nevertheless invoke std::pair's in-place constructor like the following.
MyMap m{{std::piecewise_construct, std::forward_as_tuple(1), std::tie(cfg)}};
This way, you don't have to type Alg, which kind of answers your questions. Besides, do the above only if you hate yourself and your co-workers.
Note: the in-place constructor of std::pair is actually present to allow for non-copyable, non-movable types.

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.

Why is it useful to make a const function?

why is it so useful to make a function const if you only can read variables but not write(class variable)?
If you pass something else a const pointer or const reference to an instance of your class then it can only call the class's const methods (if any).
Obviously, if you never bother with const-correctness with your types then you can ignore this.
I suppose it may also help the compiler optimize things in certain situations, although I am doubtful and, even if it did help, allowing that small improvement (if any) to dictate how you wrote your code would be a case of premature optimization in most situations.
So that you do not "accidentally" modify one of the class variables. It is just a safety measure.
(If you use the const keyword after a function that does modify any data member of the class - either directly or through another function call - you will get a compilation error).
One reason is that const is a virus. That means that if part of the code is const-correct, then the rest of the code won't interoperate with that part.
If you ignore const-correctness, chances of your classes working hand-in-hand with other libraries (beginning with the standard library) are slim.
For example:
#include <vector>
#include <algorithm>
struct X
{
int n;
bool operator< (X b)
{
return n < b.n;
}
};
int main()
{
std::vector<X> vec;
std::sort(vec.begin(), vec.end());
}
With codepad.org
In function 'const _Tp& std::__median(const _Tp&, const _Tp&, const _Tp&) [with _Tp = X]':
/usr/local/lib/gcc/i686-pc-linux-gnu/4.1.2/../../../../include/c++/4.1.2/bits/stl_algo.h:2642: instantiated from 'void std::__introsort_loop(_RandomAccessIterator, _RandomAccessIterator, _Size) [with _RandomAccessIterator = __gnu_debug::_Safe_iterator<__gnu_cxx::__normal_iterator<X*, __gnu_norm::vector<X, std::allocator<X> > >, __gnu_debug_def::vector<X, std::allocator<X> > >, _Size = int]'
/usr/local/lib/gcc/i686-pc-linux-gnu/4.1.2/../../../../include/c++/4.1.2/bits/stl_algo.h:2713: instantiated from 'void std::sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = __gnu_debug::_Safe_iterator<__gnu_cxx::__normal_iterator<X*, __gnu_norm::vector<X, std::allocator<X> > >, __gnu_debug_def::vector<X, std::allocator<X> > >]'
t.cpp:17: instantiated from here
Line 90: error: no match for 'operator<' in '__a < __b'
A stdlib compatible comparison operator must make a promise that arguments are not modified. If objects actually were to change while they are compared, an attempt to sort them would be rather futile.
Another example: you won't be able to pass arguments by const reference which is the conventional way of passing large objects. Instead you'd have to pass arguments by modifiable references. Now you won't be able to pass temporaries of any kind to your functions.
If you have a const object, it only allows const member functions to operate on it.
The more of the contract between caller and callee you can express in code instead of documentation, the more help the compiler can give you with complying with that contract (from both ends). const-ness of the implicit this pointer is an important part of that as is const-ness of all other parameter referents. (of course top-level constness of pass-by-value parameters is not part of the contract)
The benefit is that you can get the compiler to enforce where state can be modified. For example if you make a class with private data and all its methods, except, say, the constructor, are const, then you have an immutable data type. The benefit of this is not one of performance, but one of semantics.

R-value inserts don't work for unordered_map

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.