2

It's taken me a while, but I've finally constructed a minimal example with illustrates the problem I'm having.

#include <memory>
#include <vector>

class Thing
{
};

class Box
{
public:
  std::vector<std::unique_ptr<Thing>> Things;
  static Box MakeBox() {Box b; return b;}
};

My real program is obviously quite a lot more complicated than this.

GCC 4.8.3 happily compiles this. It also compiles the real application, which works perfectly.

Visual Studio 2012 insists that this code is not correct, giving me error C2248 on line 606 of vc\include\xmemory0. If I wade through several miles of compiler output, I discover the real source of the error is line 11 in the above example. (The line that defines Things.) VS also refuses to compile my real application, with the same error.

So, is this code correct or not? If it's not correct, then why does GCC accept it? And how to I make it correct? If it is correct, then why won't VS compile it? Is there some way I can unconditionally force VS to actually compile my program?


Output from VS:

1>------ Build started: Project: TestUniquePtr, Configuration: Debug Win32 ------
1>  Main.cpp
1>c:\program files (x86)\microsoft visual studio 11.0\vc\include\xmemory0(606): error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'
1>          with
1>          [
1>              _Ty=Thing
1>          ]
1>          c:\program files (x86)\microsoft visual studio 11.0\vc\include\memory(1447) : see declaration of 'std::unique_ptr<_Ty>::unique_ptr'
1>          with
1>          [
1>              _Ty=Thing
1>          ]
1>          c:\program files (x86)\microsoft visual studio 11.0\vc\include\xmemory0(605) : while compiling class template member function 'void std::allocator<_Ty>::construct(_Ty *,const _Ty &)'
1>          with
1>          [
1>              _Ty=std::unique_ptr<Thing>
1>          ]
1>          c:\program files (x86)\microsoft visual studio 11.0\vc\include\xmemory0(751) : see reference to function template instantiation 'void std::allocator<_Ty>::construct(_Ty *,const _Ty &)' being compiled
1>          with
1>          [
1>              _Ty=std::unique_ptr<Thing>
1>          ]
1>          c:\program files (x86)\microsoft visual studio 11.0\vc\include\type_traits(743) : see reference to class template instantiation 'std::allocator<_Ty>' being compiled
1>          with
1>          [
1>              _Ty=std::unique_ptr<Thing>
1>          ]
1>          c:\program files (x86)\microsoft visual studio 11.0\vc\include\vector(655) : see reference to class template instantiation 'std::is_empty<_Ty>' being compiled
1>          with
1>          [
1>              _Ty=std::allocator<std::unique_ptr<Thing>>
1>          ]
1>          d:\projects\c++\testuniqueptr\testuniqueptr\main.cpp(11) : see reference to class template instantiation 'std::vector<_Ty>' being compiled
1>          with
1>          [
1>              _Ty=std::unique_ptr<Thing>
1>          ]
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • 1
    Why are you using VS2012? –  Oct 16 '18 at 15:46
  • @NeilButterworth As far as I can tell, it's the last version of VS to support Windows XP. – MathematicalOrchid Oct 16 '18 at 15:47
  • 1
    @MathematicalOrchid That's incorrect. All recent version of VS support Windows XP. You just have to set the platform toolset to an XP compatible one. When you install VS it either comes with it or as an option you can check when you install it. I've recently used it with VS 2015 and I've seen it as available with VS 2017. Unless you mean it's the latest VS that runs on XP. That might be true. – François Andrieux Oct 16 '18 at 15:48
  • First of all, if you are asking about compiler errors, you need to provide those errors as they were output by compiler. Second of all, chances are VS2012 doesn't support `std::unique_ptr`, but I do not know for sure. – SergeyA Oct 16 '18 at 15:52
  • 3
    VS 2012 has laughable C++11 support. For full support you need VS 2015 (update 3 I think) or 2017. – NathanOliver Oct 16 '18 at 15:52
  • VS2015+ also happily compile your code snippet: https://godbolt.org/z/g6MiA4 – Trass3r Oct 16 '18 at 15:57
  • Wow, 2 downvotes and a close vote after I went to all the trouble of distilling my 8,000 line program down to a really tiny, minimal test case *and* clearly explained what I'm having trouble with? I'm surprised everybody thinks this is such a terrible question. Am I missing something? – MathematicalOrchid Oct 16 '18 at 15:59
  • @MathematicalOrchid The question is fine (I don't understand the downvotes either) and the code appears fine. This is likely a standard library implementation or compiler bug. As mentioned earlier VS2012 has limited c++11 support. It looks like either the vector is being copied or the vector is copying it's elements though it shouldn't. Unfortunately, I haven't had a VS 2012 installed for a while now, so it's hard to provide actionable advice. And I feel like this might be the situation most SO users that use VS will be in. Maybe try `return std::move(b);`? – François Andrieux Oct 16 '18 at 16:04
  • @FrançoisAndrieux If the correct answer is "update to VSxxxx will fix it" then I'm happy to accept that as an answer. If it's really that easy... I wasn't aware VS was so notorious for having poor standards support. – MathematicalOrchid Oct 16 '18 at 16:07
  • 1
    @MathematicalOrchid In case you need to convince anyone to upgrade: https://msdn.microsoft.com/en-us/library/hh567368.aspx – NathanOliver Oct 16 '18 at 16:09
  • @FrançoisAndrieux: The close vote reason is lack of error messages, which was true prior to the edit. – Ben Voigt Oct 16 '18 at 16:12
  • @BenVoigt For some reason I thought specifying the exact error code and location would be sufficient, but OK... – MathematicalOrchid Oct 16 '18 at 16:15
  • 1
    This link can be useful: https://msdn.microsoft.com/en-us/library/hh567368.aspx – Marek R Oct 16 '18 at 16:19
  • I've closed this as a dupe because the issue is `Box` is not getting a compiler generated move constructor. – NathanOliver Oct 16 '18 at 16:33
  • @MathematicalOrchid They are still struggling with the more advanced C++17 features in VS2017. – Trass3r Oct 16 '18 at 16:35
  • Using a C++ compiler than is not C++11 compatible to compile C++11 code. Does this need to be pointed out that it should not be any surprise that it does not work? Even though it may support some C++11 features, it is not C++11 compliant. This appears to be one of the not supported features. – Eljay Oct 16 '18 at 17:10

2 Answers2

4

Your problem isn't std::unique_ptr but std::vector.

Your compiler comes with an old version of std::vector that requires the element type to be copyable.

The return by value (return b;) should invoke a move of the vector, but your std::vector doesn't implement move.

std::unique_ptr is moveable but not copyable, therefore it doesn't meet the pre-C++11 requirements for being used in std::vector... a requirement which still applies to VC++ 2012.

Your best option is to use a newer compiler and standard library. One that supports move semantics on std::vector.

Otherwise you might make some progress by eliminating the copy of std::vector, for example, by having MakeBox fill in an output argument rather than returning a new object.

static void MakeBox(Box& b) { /* fill Things in b provided by caller */ }

That's probably an exercise in futility though, because whenever the vector needs to grow, it has to relocate the existing elements to new storage, and with incomplete move support, it will try to copy those.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I vaguely remember these issues now. Even thought of lack of NRVO and C++17's guaranteed copy elision but not of a missing move constructor :D – Trass3r Oct 16 '18 at 16:29
  • 2
    I don't believe this is the answer. MSVS2012 doesn't auto generate move constructors so `Box` isn't moveable. – NathanOliver Oct 16 '18 at 16:34
3

The problem is that Box has no move constructor, thus returning a Box requires it to have a copy constructor (which it can't because unique_ptr is not copyable). All you have to do is define a move constructor for Box:

Box::Box(Box&& other)
  : Things(std::move(other.Things))
{
}

With more recent editions, the compiler will generate the move constructor for you.

Peter Ruderman
  • 12,241
  • 1
  • 36
  • 58
  • How does this work if `Box` has a dozen other members? Do I have to explicitly remember to move all of those too? (Seems VS doesn't support the `= default` syntax yet.) – MathematicalOrchid Oct 17 '18 at 08:08
  • Yes. There's no way around that really (aside from refactoring to reduce the number of members). – Peter Ruderman Oct 17 '18 at 11:45