std::vector of class with private constructor does not compile when using modern C++

  • A+
Category:Languages

The code I'm working on was initially designed using C++03, and compiles and functions without errors using g++ -std=c++03. My goal is to have the same code compile using g++ -std=c++17.

The code contains a MyClass which contains a NestedClass. Only MyClassshould be able to use create and modify instances of NestedClass, which are stored in a std::vector< NestedClass >. As such NestedClass contains a private constructor, and declares MyClass and std::vector< NestedClass > as friends.

Minimal Example:

#include <vector>  class MyClass {      public:          class NestedClass {             friend class MyClass;             friend class std::vector< NestedClass >;             double _d;             NestedClass( double d = 0.0 ) : _d(d){ }         };      private:          std::vector< NestedClass > data;      public:          MyClass(){             data.resize( 40 );         }   };  int main(){     MyClass myclass = MyClass();     return 0; } 

This minimal example fails when compiling -std=c++17 with the following error:

/usr/include/c++/7/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = MyClass::NestedClass; _Args = {}]’: /usr/include/c++/7/bits/stl_uninitialized.h:527:18:   required from ‘static _ForwardIterator std::__uninitialized_default_n_1<_TrivialValueType>::__uninit_default_n(_ForwardIterator, _Size) [with _ForwardIterator = MyClass::NestedClass*; _Size = long unsigned int; bool _TrivialValueType = false]’ /usr/include/c++/7/bits/stl_uninitialized.h:583:20:   required from ‘_ForwardIterator std::__uninitialized_default_n(_ForwardIterator, _Size) [with _ForwardIterator = MyClass::NestedClass*; _Size = long unsigned int]’ /usr/include/c++/7/bits/stl_uninitialized.h:645:44:   required from ‘_ForwardIterator std::__uninitialized_default_n_a(_ForwardIterator, _Size, std::allocator<_Tp>&) [with _ForwardIterator = MyClass::NestedClass*; _Size = long unsigned int; _Tp = MyClass::NestedClass]’ /usr/include/c++/7/bits/vector.tcc:563:35:   required from ‘void std::vector<_Tp, _Alloc>::_M_default_append(std::vector<_Tp, _Alloc>::size_type) [with _Tp = MyClass::NestedClass; _Alloc = std::allocator<MyClass::NestedClass>; std::vector<_Tp, _Alloc>::size_type = long unsigned int]’ /usr/include/c++/7/bits/stl_vector.h:692:21:   required from ‘void std::vector<_Tp, _Alloc>::resize(std::vector<_Tp, _Alloc>::size_type) [with _Tp = MyClass::NestedClass; _Alloc = std::allocator<MyClass::NestedClass>; std::vector<_Tp, _Alloc>::size_type = long unsigned int]’ bug.cpp:21:20:   required from here /usr/include/c++/7/bits/stl_construct.h:75:7: error: ‘MyClass::NestedClass::NestedClass(double)’ is private within this context      { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bug.cpp:11:4: note: declared private here     NestedClass( double d = 0.0 ) : _d(d){ } 

How can I rewrite the code so that it compiles using c++17?

Changing std::vector< NestedClass > to std::vector< NestedClass * > is not an option since it would require rewriting code that uses MyClass which I do not control.

 


The minimalistic fix for your particular example is

data.resize( 40, {} ); 

Call the private constructor yourself, so that vector only needs to call the (implicitly declared) public ones.


In general, befriending something in a library you don't control doesn't work. You have no idea whether said something is actually going to delegate the work to something else.

In vector's case, it is pretty much required to delegate said work to something else.


A proper fix will likely involve changes to the classes involved. One possibility is the passkey idiom: make the constructors public, but only callable with an argument of a private type.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: