c++stdoptional

Casting std::optional inferior type


If I have an std::optional<T> then how can I transparently cast it to a std::optional<U> given that T and U are compatible types and preserving std::nullopt?

That is, if the initial std::optional<T> was a std::nullopt then the resulting std::optional<U> shall be the same. Otherwise the requested cast shall be performed (static/dynamic/reinterpret...).

struct B { };
struct C: public B { };

std::optional<C *> c;
std::optional<B *> b;

b = c; // works - a C can be implicitely up-casted to a B
//c = b; // doesn't work, explicit down-cast (involving knowledge) required

I know there is std::optional::transform, and I can work-around it by formulating if/else myself, of course.

Just asking, I guess I'm overlooking an elegant way.


Solution

  • std::optional<T> and std::optional<U> are completely separate types in general, and so converting one to the other isn't normally possible. It does work when U is constructible from T because std::optional has a converting constructor (see (4)) for this case.

    When this constructor cannot be used, you need a std::optional counterpart of std::static_pointer_cast et al. Nothing like that exists in the standard library, but you could use std::optional::transform directly, or implement such a cast yourself:

    std::optional<void*> v;
    // if v is empty, produces an empty std::optional<int*>,
    // otherwise applies the transformation, which is a static_cast
    std::optional<int*> i = v.transform([](void* p) {
        return static_cast<int*>(p);
    });
    

    If you do this a lot, here's how you can write a static_optional_cast, similar to std::static_pointer_cast:

    template <typename To, typename From>
        // might want to use a proper concept here
        requires requires (const From& from) { static_cast<To>(from); }
    std::optional<To> static_optional_cast(const std::optional<From>& from) {
        // Could also use transform here, but for the sake of demonstration,
        // let's do it "manually":
        if (!from) return {};
        return static_cast<To>(*from);
    }
    
    // ...
    
    std::optional<void*> v;
    std::optional<int*> j = static_optional_cast<int*>(v);