I have the following piece of code
// DoSomething.h
class Foo;
void doSomething(std::optional<std::unique_ptr<const Foo>> f = std::nullopt);
If I include DoSomething.h
anywhere without the definition of Foo
the file doesn't compile, even if it doesn't even call the doSomething
function. In contrast, removing the =std::nullopt
default argument, everything compiles fine.
My assumption is the issue has something to do with the deleter interface of the unique_ptr, and my question is how to solve this issue so I can forward declare Foo
in this scenario?
Thanks
Edit: I don't want to change the signature of this function, I know I can solve this with overloading or removing the optional use. I want to understand why this doesn't work.
Following note - this same code compiles perfectly fine when switching the std::unique_ptr
with std::shared_ptr
. It means it should be possible to make it work with std::unique_ptr
. I know their deleteres have slightly different interface, and I'm not familiar enough with it.
Cppreference says the following about the template argument of std::optional
:
T - the type of the value to manage initialization state for. The type must meet the requirements of Destructible
std::unique_ptr<T>
is not destructible when T
is incomplete.
The default arguments of a function are instantiated at the declaration of that function.
Therefore the requirements of std::optional
are not satisfied at the point where the object is instantiated.
As noted in the comments, you can solve the issue by overloading the function instead of using default arguments. Also pointers expose a null value, so in most cases std::optional<pointer-type>
is not necessary.
Follow up after reading your edit:
class Foo;
using FooDeleter = std::function<void(Foo const*)>;
void doSomething(std::optional<std::unique_ptr<const Foo, FooDeleter>> f = std::nullopt);
This code will compile fine. It uses std::unique_ptr
with a type erased deleter similar to std::shared_ptr
. The problem if you instantiate unique_ptr
with the default deleter (std::default_delete
) is this:
A simplified definition of std::default_delete
would look something like this:
template <typename T>
class default_delete {
void operator()(T* p) const {
p->~T();
}
};
So during the instantiation of std::optional<std::unique_ptr<Foo>>
the code above will be instantiated. Because it makes a call to the destructor of Foo
that is not declared compilation will fail.
If you instantiate std::optional<std::unique_ptr<Foo, FooDeleter>>
, only operator()
of std::function<...>
will be called, which is independent of the definition of the destructor of Foo
.