10

I get compilation errors on g++ (GCC) 4.7.2 but not on MSVC-2012 when trying to std::vector::push_back a non-copyable (private copy constructor) but moveable object. To me my example looks identical to many other examples on SO and elsewhere. The error message makes it looks like a problem with the struct not being 'direct constructible' - I don't know what this means so am doubly unsure about why an object needs to be 'direct constructible' to be pushed back.

#include <vector>
#include <memory>

struct MyStruct
{

    MyStruct(std::unique_ptr<int> p);
    MyStruct(MyStruct&& other);
    MyStruct&  operator=(MyStruct&& other);

    std::unique_ptr<int> mP;

private:
            // Non-copyable
    MyStruct(const MyStruct&);
    MyStruct& operator=(const MyStruct& other);
};

int main()
{

    MyStruct s(std::unique_ptr<int>(new int(5)));
    std::vector<MyStruct> v;

    auto other = std::move(s);       // Test it is moveable
    v.push_back(std::move(other));   // Fails to compile

    return 0;
}

Gives errors

/usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/type_traits: In instantiation of ‘struct std::__is_direct_constructible_impl<MyStruct, const MyStruct&>’:
... snip ...
/usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/bits/stl_vector.h:900:9:   required from ‘void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = MyStruct; _Alloc = std::allocator<MyStruct>; std::vector<_Tp, _Alloc>::value_type = MyStruct]’
main.cpp:27:33:   required from here
main.cpp:16:5: error: ‘MyStruct::MyStruct(const MyStruct&)’ is private

Simple workaround from various answers:

  • Use MyStruct(const MyStruct&) = delete; instead of private ctor hack
  • Inherit boost::noncopyable (or another class with private ctor)
Xeo
  • 129,499
  • 52
  • 291
  • 397
Zero
  • 11,593
  • 9
  • 52
  • 70
  • That code should indeed compile perfectly fine, sounds like some weird SFINAE problem. Can you try `.emplace_back(std::move(other))`? – Xeo Dec 10 '12 at 12:24
  • Identical error with `emplace_back` (I'd already tried that - along with a few incantations ;) – Zero Dec 10 '12 at 12:26
  • What happens if you try just `std::unique_ptr`? – Xeo Dec 10 '12 at 12:28
  • That works fine - my prod code already has that solution checked in. Trying to figure this out for my own education. – Zero Dec 10 '12 at 12:31
  • 2
    Okay, try to `= delete` the copy members instead of making them private. – Xeo Dec 10 '12 at 12:34
  • Bingo. Although that's a deal breaker for me since I need compatibility with MSVS2012, it's nice to know. Also, inheriting `boost::noncopyable` works, although it's not recommended as the compiler errors make it impossible to find the accidental copy operation. Conclusion: use `unique_ptr` where appropriate but don't make classes explicitly movable - instead wrap in a pointer as for non-copyable classes pre-C++11. – Zero Dec 10 '12 at 12:42
  • 2
    Also you need to add noexcept on your move constructor and your move assignment operator, otherwise gcc vector since 4.7 refuse to move anything anyway : http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52745 – Arzar Dec 10 '12 at 14:40
  • @ThomasPetit, that's not quite accurate, it will still move non-copyable types even if the move might throw ... the problem here is that G++ 4.7 cannot detect the type is non-copyable – Jonathan Wakely Dec 10 '12 at 22:53
  • @Jonathan Wakely : thanks a lot for the correction! I got fooled by the non-detection of non-copyablity when marking copy ctor private. Your answer really cleared up a lot about the behavior of gcc 4.7. – Arzar Dec 10 '12 at 23:20

1 Answers1

15

The failure is due to a limitation of G++ 4.7, which doesn't implement DR 1170, which was changed very late in the C++11 standardisation process to say that access checking should be done as part of template argument deduction.

The underlying cause is that libstdc++'s vector will move elements if the move operation is guaranteed not to throw (i.e. it's declared noexcept or throw()), otherwise if the type is copyable the elements will be copied, otherwise if the type is not copyable but does have a possibly-throwing move operation then it will be moved (and if an exception is thrown the results are undefined unspecified.) This is implemented with checks to the is_nothrow_move_constructible and is_copy_constructible type traits. In your case, the type is not nothrow move constructible, so the is_copy_constructible trait is checked. Your type has a copy constructor but it's not accessible, so the is_copy_constructible trait produces a compiler error with G++ 4.7 because access checking is not done during template argument deduction.

If you make your move constructor and move assignment operator noexcept then the type will be moved and doesn't need to be copyable, so the is_copy_constructible trait that fails is not used, and the code compiles OK.

Alternatively, (as also stated in the comments) if you make the copy constructor deleted then the is_copy_constructible trait gets the right result.

Another alternative is to use something like boost::noncopyable which implicitly makes the copy constructor deleted so the is_copy_constructible trait works properly (and also works with older compilers like MSVC that don't support deleted functions properly). I don't know what you mean about making it impossible to find the error, does MSVC not show you the full context of a compiler error?

Conclusion: use unique_ptr where appropriate but don't make classes explicitly movable

I disagree with this conclusion, it is too extreme. Instead make your classes nothrow movable whenever possible. Also, when possible, use deleted functions to make a type non-copyable instead of private+unimplemented functions, maybe using a macro for portability to older compilers e.g.

#if __cplusplus >= 201103L
#define NONCOPYABLE(TYPE) \
  TYPE(const TYPE&) = delete; TYPE& operator=(const TYPE&) = delete
#else
// must be used in private access region
#define NONCOPYABLE(TYPE) \
  TYPE(const TYPE&); TYPE& operator=(const TYPE&)
#endif

struct MyStruct
{
...
private:
    NONCOPYABLE(MyStruct);
};
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Just to clarify: Is the original code standards compliant or not? re conclusion - I was referring more to the company coding standards (which exist to be disagreed with!). Juggling MSVC and gcc compliance, there's a balance between style that avoid these 'suprises', difficult to solve error messages (from `boost::noncopyable`) and satisfactory performance. In particular, until MSVC has compiler generated move ctor in line with the latest standard, it's too much boilerplate for too little benefit. – Zero Dec 10 '12 at 23:15
  • The code is valid, it's rejected because G++ 4.7 cannot implement a standard-conforming `is_copy_constructible` due to not checking access during template argument deduction (i.e. it doesn't implement [DR 1170](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1170)). The code works with G++ 4.8 or recent versions of Clang++, as they implement DR 1170 – Jonathan Wakely Dec 10 '12 at 23:43
  • this DR1170 is totally game changing. We have manyyyyy type traits which fails because of private declared stuff. This is always mentioned as trait limitations in documents. It seems now that after DR1170, traits will not have limitations due to access problems ? Example: http://www.boost.org/doc/libs/1_58_0/libs/type_traits/doc/html/boost_typetraits/reference/has_bit_or.html paragraph `Known issues` **This trait cannot detect whether binary operator| is public or not: if operator| is defined as a private member of Lhs then instantiating has_bit_or will produce a compiler error.** – v.oddou Jun 04 '15 at 03:11
  • @v.oddou, yes, this problem was solved 4 years ago in C++11. Welcome to the future, you'll like it here :) GCC 4.8.0 implements the C++11 rules. – Jonathan Wakely Jun 04 '15 at 10:07
  • I wish I were in the future with all of you guys, but I have to write cross platform code including older SDK like playstation portable, or nintendo 3DS. But we're almost there, just slightly behind. playstation vita has reasonable C++11 support, we've migrated all windows projects to VC12. But this DR1170 is really a game changer, I see many weeks of work for the boost guys just to update the documentation. It could also give a serious code cleaning opportunity. In many places I have inherited from "xx-able" classes to "guide" traits. I think boost and stdlib also has a lot of these. – v.oddou Jun 05 '15 at 01:22