2

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?

5
  • please post the code that has the error together with the compiler error message. Asking us to modify the code before we can see the error is a source of confusion and misunderstanding (even if the modification might seem minor and/or obvious) Commented Feb 5 at 16:10
  • why would you think the constructor is not required: godbolt.org/z/55331EjM9 Commented Feb 5 at 16:12
  • godbolt.org/z/WsW6WdP19 Commented Feb 5 at 16:19
  • i am still curious what else you expected and why. My guess is that you expected emplace to be some kind of "magic" that doesnt need to call the constructor, or maybe something else ... Commented Feb 5 at 16:27
  • 1
    I expected that if return A(...) works then o.emplace(...) works (I suppose that since makeA is a friend then anything under its context is also friend of A). Commented Feb 5 at 16:30

2 Answers 2

7

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.

Sign up to request clarification or add additional context in comments.

Comments

7

Small extension to existing answer. To overcome this issue you can use lock key pattern:

class A {
    class LockKey {
        friend std::optional<A> makeA(int x, int y);

    private:
        LockKey() { }
        LockKey(const LockKey&) = default;
    };

public:
    A(int x, int y, const LockKey&)
        : x(x)
        , y(y)
    {
    }

private:
    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) {
#ifndef USE_EMPLACE
        return A(x, y, A::LockKey {});
#else
        o.emplace(x, y, A::LockKey {});
#endif
    }
    return o;
}

https://godbolt.org/z/sY4z4vWd9

7 Comments

if it was a plain method it would be simple to deduce the type of LockKey and construct one. I am not aware of a way to easily get type of 3rd argument of a constructor though
@Marek: Still bypassable ;-) Demo
there you go, too easy to deduce ;)
@463035818_is_not_an_ai: traits to get type of parameter of constructor might be done with stateful meta programming, so not an easy way.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.