c++c++17stdoptional

Return std::optional<T> where T's constructor is private


I still do not understand the behavior of std::optional in the following code:

class A
{
public:
  // A(int x, int y) : x(x), y(y) {} // always compiles
private:
  A(int x, int y) : x(x), y(y) {}    // does not compile if using o.emplace(x, y)
  friend std::optional<A> makeA(int x, int y);

  int x, y;
};

std::optional<A> makeA(int x, int y)
{
  std::optional<A> o;
  if (x != 0 && y != 0) {
    return A(x, y);
    // o.emplace(x, y);
  }
  return o;
}

If I make the constructor of A as public then in makeA(...) I can use either return A(x, y) or o.emplace(x, y), boths compile.

But if I make the constructor of A private then o.emplace(x, y) will not compile. As far as I understand from reading the message error of templates:

error: no matching function for call to ‘std::optional<A>::emplace(int&, int&)’
   70 |     o.emplace(x, y);
      |     ~~~~~~~~~^~~~~~
In file included from test.cpp:2:
/usr/include/c++/11/optional:871:9: note: candidate: ‘template<class ... _Args> std::enable_if_t<is_constructible_v<_Tp, _Args ...>, _Tp&> std::optional<_Tp>::emplace(_Args&& ...) [with _Args = {_Args ...}; _Tp = A]’
  871 |         emplace(_Args&&... __args)
      |         ^~~~~~~
/usr/include/c++/11/optional:871:9: note:   template argument deduction/substitution failed:
In file included from /usr/include/c++/11/bits/move.h:57,
                 from /usr/include/c++/11/bits/stl_pair.h:59,
                 from /usr/include/c++/11/bits/stl_algobase.h:64,
                 from /usr/include/c++/11/memory:63,
                 from test.cpp:1:
/usr/include/c++/11/type_traits: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = A&]’:
/usr/include/c++/11/optional:871:2:   required by substitution of ‘template<class ... _Args> std::enable_if_t<is_constructible_v<A, _Args ...>, A&> std::optional<A>::emplace<_Args ...>(_Args&& ...) [with _Args = {int&, int&}]’
test.cpp:70:14:   required from here
/usr/include/c++/11/type_traits:2579:11: error: no type named ‘type’ in ‘struct std::enable_if<false, A&>’
 2579 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;
      |           ^~~~~~~~~~~
In file included from test.cpp:2:
/usr/include/c++/11/optional:883:9: note: candidate: ‘template<class _Up, class ... _Args> std::enable_if_t<is_constructible_v<_Tp, std::initializer_list<_Up>&, _Args ...>, _Tp&> std::optional<_Tp>::emplace(std::initializer_list<_Up>, _Args&& ...) [with _Up = _Up; _Args = {_Args ...}; _Tp = A]’
  883 |         emplace(initializer_list<_Up> __il, _Args&&... __args)
      |         ^~~~~~~
/usr/include/c++/11/optional:883:9: note:   template argument deduction/substitution failed:
test.cpp:70:14: note:   mismatched types ‘std::initializer_list<_Tp>’ and ‘int’
   70 |     o.emplace(x, y);

the class A is not constructible. But how is it possible?


Solution

  • std::optional<A>::emplace forwards its parameters to A's constructor. The constructor is private, hence not accesible by emplace. You declared makeA to be a friend but that does not transitively apply to other functions called by makeA. Hence, the error.

    You can call the constructor directly because makeA is declared a friend of A. Hence it can access the private constructor.