9

I am working on a library which uses a struct which should not have the default constructor accessible by users of the lib.

struct Example
{
    Example(int x);

  private:
    Example();
};

Inside the library the the default constructor is required for std::map to create new entries. The library is very careful in actually putting values everywhere where the default constructor is used.

The library uses a map to store these structs, like this:

std::map<int, Example> data;

Check HERE FOR A COMPLETE EXAMPLE in ideOne.

I would like to prevent users of the lib to be able to use the default constructor. How can I befriend std::map, std::pair and/or std::tuple to allow std::map to use this default constructor?

friend class std::map<int, Example>;
friend class std::pair<int, Example>;

do not work and i am not sure how to friend the following constructor which complains about beeing unable to access the default constructor:

// TEMPLATE CONSTRUCTOR pair::pair(tuple, tuple, sequence, sequence)
template<class _Ty1,
class _Ty2>
template<class _Tuple1,
    class _Tuple2,
    size_t... _Indexes1,
    size_t... _Indexes2> inline
    pair<_Ty1, _Ty2>::pair(_Tuple1& _Val1,
        _Tuple2& _Val2,
        integer_sequence<size_t, _Indexes1...>,
        integer_sequence<size_t, _Indexes2...>)
    : first(_STD get<_Indexes1>(_STD move(_Val1))...),
        second(_STD get<_Indexes2>(_STD move(_Val2))...)
    {   // construct from pair of tuples
    (void) _Val1;   // TRANSITION, VSO#181496
    (void) _Val2;
    }

Any support is much appreciated!


Edit: full error message:

In file included from /usr/include/c++/6/bits/stl_map.h:63:0,
                 from /usr/include/c++/6/map:61,
                 from prog.cpp:2:
/usr/include/c++/6/tuple: In instantiation of ‘std::pair<_T1, _T2>::pair(std::tuple<_Args1 ...>&, std::tuple<_Args2 ...>&, std::_Index_tuple<_Indexes1 ...>, std::_Index_tuple<_Indexes2 ...>) [with _Args1 = {int&&}; long unsigned int ..._Indexes1 = {0ul}; _Args2 = {}; long unsigned int ..._Indexes2 = {}; _T1 = const int; _T2 = Example]’:
/usr/include/c++/6/tuple:1579:63:   required from ‘std::pair<_T1, _T2>::pair(std::piecewise_construct_t, std::tuple<_Args1 ...>, std::tuple<_Args2 ...>) [with _Args1 = {int&&}; _Args2 = {}; _T1 = const int; _T2 = Example]’
/usr/include/c++/6/ext/new_allocator.h:120:4:   required from ‘void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::pair<const int, Example>; _Args = {const std::piecewise_construct_t&, std::tuple<int&&>, std::tuple<>}; _Tp = std::_Rb_tree_node<std::pair<const int, Example> >]’
/usr/include/c++/6/bits/alloc_traits.h:455:4:   required from ‘static void std::allocator_traits<std::allocator<_CharT> >::construct(std::allocator_traits<std::allocator<_CharT> >::allocator_type&, _Up*, _Args&& ...) [with _Up = std::pair<const int, Example>; _Args = {const std::piecewise_construct_t&, std::tuple<int&&>, std::tuple<>}; _Tp = std::_Rb_tree_node<std::pair<const int, Example> >; std::allocator_traits<std::allocator<_CharT> >::allocator_type = std::allocator<std::_Rb_tree_node<std::pair<const int, Example> > >]’
/usr/include/c++/6/bits/stl_tree.h:543:32:   required from ‘void std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_construct_node(std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Link_type, _Args&& ...) [with _Args = {const std::piecewise_construct_t&, std::tuple<int&&>, std::tuple<>}; _Key = int; _Val = std::pair<const int, Example>; _KeyOfValue = std::_Select1st<std::pair<const int, Example> >; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, Example> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Link_type = std::_Rb_tree_node<std::pair<const int, Example> >*]’
/usr/include/c++/6/bits/stl_tree.h:560:4:   required from ‘std::_Rb_tree_node<_Val>* std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_create_node(_Args&& ...) [with _Args = {const std::piecewise_construct_t&, std::tuple<int&&>, std::tuple<>}; _Key = int; _Val = std::pair<const int, Example>; _KeyOfValue = std::_Select1st<std::pair<const int, Example> >; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, Example> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Link_type = std::_Rb_tree_node<std::pair<const int, Example> >*]’
/usr/include/c++/6/bits/stl_tree.h:2196:64:   required from ‘std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_emplace_hint_unique(std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::const_iterator, _Args&& ...) [with _Args = {const std::piecewise_construct_t&, std::tuple<int&&>, std::tuple<>}; _Key = int; _Val = std::pair<const int, Example>; _KeyOfValue = std::_Select1st<std::pair<const int, Example> >; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, Example> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator = std::_Rb_tree_iterator<std::pair<const int, Example> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::const_iterator = std::_Rb_tree_const_iterator<std::pair<const int, Example> >]’
/usr/include/c++/6/bits/stl_map.h:502:8:   required from ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](std::map<_Key, _Tp, _Compare, _Alloc>::key_type&&) [with _Key = int; _Tp = Example; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, Example> >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = Example; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’
prog.cpp:17:8:   required from here
/usr/include/c++/6/tuple:1590:70: error: ‘Example::Example()’ is private within this context
         second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)
                                                                      ^
prog.cpp:11:2: note: declared private here
  Example(){}
  ^~~~~~~
Beginner
  • 5,277
  • 6
  • 34
  • 71
  • Could you consolidate it into a single program that shows an error instead of snippets (one is also missing a semi-colon)? – StoryTeller - Unslander Monica May 23 '18 at 10:21
  • @StoryTeller yes, of course. Should have done that right away :) – Beginner May 23 '18 at 10:27
  • The error is due to the constructor being private. If you do not have atleast one public constructor, then you cannot create instances of this class. Also, you want the users to not use the constructor? – Raghav May 23 '18 at 10:33
  • Check this one out. https://stackoverflow.com/questions/17624620/c-friend-constructor – Raghav May 23 '18 at 10:39

3 Answers3

5

First, I would like to correct an important point in your post:

Inside the library the default constructor is required for std::map

You can use std::map<K,T> even if T has no default constructor. See this post. In this case you cannot use operator[] to read & write into the map. You can still do it with other methods:

  • read the map: V value = map.at(key);
  • insert into the map: map.insert(std::make_pair(key, value));.

I strongly advise to do it this way.


That being said, if you really want go down the "private constructor & friend" way, from your error message:

In instantiation of ‘std::pair<_T1, _T2>::pair(/*...*/) [with /*...*/; _T1 = const int; _T2 = Example]’

You can try to friend std::pair<const int, Example>;. As Caleth said in his answer, this might not be portable.

  • I found insert_or_assign useful as well: it returns more information than operator[] and does not require default-constructibility of the mapped type. – Beginner May 24 '18 at 15:00
2

There is no portable solution.

Your type is not DefaultConstructible, so trying to data[5] is undefined behaviour.

For reference, I tried all the following and g++ still rejected it

struct Example
{
    Example(int x) {}

  private:
    Example();
    friend class std::map<int, Example>;
    friend std::map<int, Example>::key_type;
    friend std::map<int, Example>::mapped_type; // warning: class 'Example' is implicitly friends with itself
    friend std::map<int, Example>::value_type;
    friend std::map<int, Example>::size_type;
    friend std::map<int, Example>::difference_type;
    friend std::map<int, Example>::key_compare;
    friend std::map<int, Example>::allocator_type;
    friend std::map<int, Example>::reference;
    friend std::map<int, Example>::const_reference;
    friend std::map<int, Example>::pointer;
    friend std::map<int, Example>::const_pointer;
    friend std::map<int, Example>::iterator;
    friend std::map<int, Example>::const_iterator;
    friend std::map<int, Example>::reverse_iterator;
    friend std::map<int, Example>::const_reverse_iterator;
    friend std::map<int, Example>::node_type;
    friend std::map<int, Example>::insert_return_type;
    friend std::map<int, Example>::value_compare;
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Though I am not certain how a solution would look like, wouldn't it be possible to use a custom allocator instead of the default `std::allocator` to call `Example(int)` instead of `Example()`? That's what I understand when reading 2) in http://en.cppreference.com/w/cpp/container/map/operator_at – andreee May 23 '18 at 10:52
  • @andreee *maybe*? That also changes the type of `data`, which may not be an option – Caleth May 23 '18 at 10:58
  • Much easier to replace `data[5] = Example(5)` with `data.insert_or_assign(5, Example(5));` – Caleth May 23 '18 at 11:03
  • would your examples above work with the const specifier in the first map template argument? – Beginner May 26 '18 at 08:08
1

Not sure if this is normative, but my GCC accepted:

struct Example 
{
    Example(int x) { }
    private:
    friend class std::pair<int const, Example>;
    Example() { }
};

Be aware that std::pair<int, Example> won't work as std::map's keys are const!

Aconcagua
  • 24,880
  • 4
  • 34
  • 59