I get this strange compilation error on new versions of gcc (4.9+).
Here is the code:
using namespace std;
template <typename T>
holder() = default;
holder(const holder& b)
holder& operator=(const holder& h)
t = h.t;
holder& operator=(holder&& h)
t = std::move(h.t);
typedef map<std::string, y_u_no_elision> mymap;
auto m = foo();
m = foo();
Short answer: It's a bug in libstdc++. According to the allocator-aware container requirements table in [container.requirements.general] in the Standard (hasn't changed since C++11), container move assignment:
X is the container type and
T is its
You're using the default allocator, which has
using propagate_on_container_move_assignment = true_type;, so the above requirement doesn't apply; there should be no special requirements on the
Quick fix: If you cannot touch
holder, one solution is to change
y_u_no_elision(const y_u_no_elision&) = delete; y_u_no_elision(y_u_no_elision&&) = default;
Long story: The bug is essentially caused by this line in stl_tree.h.
_Rb_tree is the underlying implementation of
std::map and that line in its move assignment operator definition basically does the check specified by the Standard quote above. However, it does it using a simple
if, which means that, even if the condition is satisfied, the other branch has to compile as well, even though it won't be executed at runtime. Lacking the shiny new C++17
if constexpr, this should be implemented using something like tag dispatching (for the first two conditions - the third one is a true runtime check), in order to avoid instantiating the code outside the taken branch.
The error is then caused by this line, which uses
value_type. And here comes the long story.
std::pair<const std::string, y_u_no_elision>.
In your initial code:
holderhas non-deleted, non-noexcept copy and move constructors.
y_u_no_elisionwill also be non-deleted and non-noexcept.
const value_type&instead of
value_type&&(it falls back to copy if it can - see these docs).
y_u_no_elisionto be called, which will cause
holder<ptrwrap>'s copy constructor definition to be instantiated, which tries to copy a
Now, if you remove the user-declared copy and move constructors and assignment operators from
holderwill get the implicitly-declared ones. The copy constructor will be deleted and the move constructor will be defaulted, not deleted and
value_type, with one exception unrelated to
value_type's move constructor will try to move from a
const std::string; this will not call
string's move constructor (which is
noexceptin this case), but rather its copy constructor, as
string&&cannot bind to an rvalue of type
string's copy constructor is not
noexcept(it may have to allocate memory), so
value_type's move constructor won't be either.
std::move_if_noexcept: it returns an rvalue reference even if the argument's move constructor isn't
noexcept, as long as the argument is not copy-constructible (it falls back to
noexceptmove if it cannot fall back to copy); and
value_typeisn't, because of
holder'sdeleted copy constructor.
This is the logic behind the quick fix above: you have to do something to make
value_type have a valid move constructor and a deleted copy constructor, in order to get an rvalue reference from
move_if_noexcept. This is because you won't be able to make
value_type have a
noexcept move constructor due to the
const std::string, as explained above.