In your example, where _data is a non-const pointer to a std::optional (it doesn't matter that the std::optional holds a const void type), you're trying to assign a pointer to a const object (of unknown void type) to a non-const pointer.
This violates the contract of your class constructor, where you promise not to modify the object being pointed at (not just in the constructor, but ever). Consider this simplified example:
template <typename T> Test { protected: T* _data; public: explict Test(const void * data = nullptr) { _data = data; // ERROR: can't assign a const-pointer to a non-const pointer } }
Your issue has nothing to do with std::optional.
Consider that you might not even need to wrap your pointer in a std::optional, since pointers already have the natural nullable-concept of nullptr:
class Test { protected: const void* _data; public: explict Test(void const *data = nullptr) { _data = data; } void do_thing() const { if (_data) { // nullable semantics naturally work on pointers, no need for `std::optional` // UNLESS `nullptr` isn't "null enough" for your application // do (const-qualified) operation on _data. } } }
On the concept of std::optional, pointers, and nullable
it's the responsibility of the caller of the function to check for the null for the returned value
correct - and your user would have to do with a std::optional anyway.
Another option is to specify that it's "undefined behavior" or "illegal" for a user of your class to construct it with a nullptr. And that it's not a valid state for the object to exist with a nullptr. That shifts the responsibility on the "creator" of the object, and not the "user" (even though they're likely the same human). This all depends on whether it's valid for your _data to be nullptr.
Decide what the valid states of your object are allowed to be, and whose responsibility it is to enforce that. If you design your interface correctly, you can eliminate a lot of if checks that would otherwise need to be scattered throughout all layers of the code.
Some of the responsibility lives with the object creator (user), some of the responsibility lives within the class (this could be shared with the user-creator for validity checks, or ignored), and some lies with the object user (application developer).
void const*is also equivalent toconst void*.constin a type specifier refers to the "thing" to the left ofconst, except in the special case where the "first" thing in your type should beconst, and you can placeconstto either the left or right of it. I think this is just for readability - maybe backwards compatability. Someone else can speak on the historical reasons. I personally preferconst void*tovoid const*, but you can find both in the wild. It's just a style thing.void const*andconst void*, I also preferconstfirst, because this is what i have been used to in other languages. Thanks.std::optionalanyway. Now, another option is to specify that it's "undefined behavior" or "illegal" for a user of your class to construct it with anullptr. And that it's not a valid state for the object to exist with anullptrsitting inside of it. That puts the responsibility on the "creator" of the object, and not the "user" (even though they're likely the same human). This all depends on whether it's valid for your _data to be nullptrconstexpr static char const *const NAME = "Test";is aconstpointer (the value of the pointer shall not be changed) to aconst char(the value of thechars in the array shall not be changed). Theconstexprmakes some of this redundant, sinceconstexprimpliesconstanyway.