20

It seems that adding a default constructor prevents from calling emplace_back and produces the error message: "static assertion failed: type is not assignable" (gcc 5.3 with -std=c++14). Here is a simple code that illustrates the issue:

class A { public: int a; A() = default; A(int a) { this->a = a; } A(A const & a) = delete; A& operator =(A const & a) = delete; A(A && a) = default; A& operator =(A && a) = default; }; int main() { A a(4); std::vector<A> vec; vec.emplace_back(std::move(a)); // Error: type is not assignable return 0; } 

When removing the default constructor, the error goes away! Also, if the default constructor is defined (even if it does nothing), the error also goes away:

class A { public: int a; A() { } A(int a) { this->a = a; } A(A const & a) = delete; A& operator =(A const & a) = delete; A(A && a) = default; A& operator =(A && a) = default; }; int main() { A b; A a(4); std::vector<A> vec; vec.emplace_back(std::move(a)); // Error gone return 0; } 

It seems that "A() = default;" is what is causing the problem. Is this normal behaviour on part of the compiler or is it a bug?

9
  • clang displays the same behavior as gcc Commented Jan 25, 2016 at 21:37
  • The code compiles fine with gcc 4.7.2 under c++11 Commented Jan 25, 2016 at 21:43
  • 2
    It affects the trivialness of the class. Commented Jan 25, 2016 at 21:48
  • 3
    @jaggedSpire clang compiles the code if you use libc++. This looks like a bug with libstdc++ where it's probably optimizing emplace_back for trivial types to a memcpy call, and that call chain involves calling a function that asserts that the type is copy assignable. Commented Jan 25, 2016 at 21:56
  • 1
    @rustyx, A() = default; might be trivial, but A() {} definitely isn't. That can affect optimisations (as in this case, although here the std::lib's attempt to optimise has a bug). Commented Jan 26, 2016 at 13:04

1 Answer 1

16

It's a libstdc++ bug (edit: reported as bug 69478).

Briefly, libstdc++'s std::vector, as relevant here, uses std::uninitialized_copy (paired with move iterators) to move elements on reallocation, which is reduced to std::copy if the type is trivial and the iterators' reference types are assignable (i.e., the assignment operator that would conceptually be used is usable).

Then, the std::copy for pointers to trivial types (or in our case, a move_iterator wrapping a pointer) is in turn optimized into a call to memmove coupled with a check for is_copy_assignable. Of course, that check is wrong in this case, since the uninitialized_copy, paired with move iterators, only requires the thing to be move constructible.

When you don't have a default constructor or if the default constructor is user-defined, then the class isn't trivial, so you don't hit the code path that triggers this bug.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.