Convert a variant into an optional?

105 views
Skip to first unread message

torto...@gmail.com

unread,
May 15, 2018, 11:39:10 AM5/15/18
to ISO C++ Standard - Future Proposals
Hi,
    Apologies if this has been asked before. variant and optional are quite common search terms.

    I'm wondering if there is, or should be a standard way to convert a variant into an optional?
Or if its a bad idea why there shouldn't be.

Consider the following example. Imagine Foo is an object representing a configuration.
One part of the configuration bar has two possible modes of operation one requires one piece of configuration
and one requires the other but it is not valid to specify both.

#include <variant>
#include <optional>

class Foo
{
private:
    std::variant<int, std::string> bar;
public:
    Foo(int a): bar(a) {}
    Foo(const std::string& b): bar(b) {}

    std::optional<int> getInt() const
    {
        std::optional<int> ret;
        auto got = std::get_if<int>(&this->bar);
        if (got) ret = *got;
        return ret;
    }

    std::optional<std::string> getString() const
    {
        std::optional<std::string> ret;
        auto got = std::get_if<std::string>(&this->bar);
        if (got) ret = *got;
        return ret;
    }
};

There seems to be a lot of boilerplate to convert the variant into a optional for the return.
There is an inefficiency here in that the type contained has to be checked twice to use this.

There are other ways to do this. You can expose the variant member and use a visitor
but using a visitor feels like overkill for this. Also the variant is an internal implementation detail.
An equally valid implementation would have two different optional members (but would not enforce their mutual exclusivity).

So do we need (or is there already) something kind of like:

    std::optional<std::string> getString() const
    {
        return make_optional<std::string>(this->bar);
    }

or bikeshedding:

    std::optional<std::string> getString() const
    {
        return to_optional<std::string>(this->bar);
    }


Regards,

Bruce.

Barry Revzin

unread,
May 15, 2018, 11:57:50 AM5/15/18
to ISO C++ Standard - Future Proposals


On Tuesday, May 15, 2018 at 10:39:10 AM UTC-5, Bruce Adams wrote:
Hi,
    Apologies if this has been asked before. variant and optional are quite common search terms.

    I'm wondering if there is, or should be a standard way to convert a variant into an optional?
Or if its a bad idea why there shouldn't be.

Well, one problem is that you have to copy because there's no optional reference. Some people would say the T* you get back from get_if() is already kind of optional? But in any case, this is really straightforward to implement yourself, so is it sufficiently common that everyone will re-implement themselves?

template <typename T>
auto ptr_to_optional(T* ptr) {
   
return ptr ? optional<remove_cv_t<T>>(*ptr) : nullopt;
}

template <typename T, typename ...Types>
constexpr auto variant_to_optional(variant<Types...> const& v) noexcept {
   
return ptr_to_optional(get_if<T>(&v));
}

template <size_t I, typename ...Types>
constexpr auto variant_to_optional(variant<Types...> const& v) noexcept {
   
return ptr_to_optional(get_if<I>(&v));
}

 +/- typos, naming choice, etc.

Richard Hodges

unread,
May 15, 2018, 2:42:51 PM5/15/18
to std-pr...@isocpp.org
Well, one problem is that you have to copy because there's no optional reference

What on earth possessed the committee to come to the conclusion that this was a good idea?

boost::optional of course has optional references. So once again, in reality, boost will remain the standard - while the official standard remains a useless artefact.

Another shameful failure.


--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/cd369c5c-cb53-4223-83cc-bb0497448e79%40isocpp.org.

Nevin Liber

unread,
May 15, 2018, 3:06:13 PM5/15/18
to std-pr...@isocpp.org
On Tue, May 15, 2018 at 1:42 PM Richard Hodges <hodg...@gmail.com> wrote:
Well, one problem is that you have to copy because there's no optional reference

What on earth possessed the committee to come to the conclusion that this was a good idea?

Vicente J. Botet Escriba

unread,
May 15, 2018, 5:34:36 PM5/15/18
to std-pr...@isocpp.org, torto...@gmail.com
The conversion is simple, but we don't need to do the conversion until
requested. We can see a variant<Ts...> as some kind of ValueOrNone type
(that can not change of alternative) once we select one of the
alternatives T. Lets name this function select<T>(SumType).

The conversion would be done only when requested, e.g. by an implicit
conversion.

    optional<T> o = select<T>(SumType);


However, we could as well

    auto von = select<T>(SumType);

von is not an optional<T>. It is something else, a reference to a
sum-type, with the perspective of only one of the alternatives. Now we
can use von as a ValueOrNone, with the usual von.has_value() and *von.
Note that these functions don't need to check the type twice:
    von.has_value() should be equivalent to std::get_if<T>(&von) and
    *von equivalent to std::get<T>(von).


Just my 2cts.

Vicente

torto...@gmail.com

unread,
May 17, 2018, 6:30:12 AM5/17/18
to ISO C++ Standard - Future Proposals, torto...@gmail.com
I very much like the name select.

I see your argument that a ValueOrNone isn't strictly an optional but if it isn't don't we complicate the language
with yet another "maybe" type? Wouldn't the language be cleaner with just one?
I guess the answer is adding optional references as previously mentioned?

Regards,

Bruce.

 

Nicol Bolas

unread,
May 17, 2018, 11:03:29 AM5/17/18
to ISO C++ Standard - Future Proposals, torto...@gmail.com
The thing is, a new type would be able to avoid the big design problem with `optional<T&>`: the meaning of assignment. `optional`, by design, is meant to be rebindable, modifiable. You can empty it, put a new value into it, copy a new value into its current value.

So a hypothetical `optional<T&>` ought to be rebindable too. But what does assignment mean in that context? If you assign a glvalue to an unengaged `optional<T&>` it should take a reference to it. But if you assign to an engaged `optional<T&>`, will it take a reference or copy the value? Some would expect assignment to always take a reference, and some will expect assignment to copy or reference based on engagement. Some might even expect it to always copy the value, since that's how `optional<T>` works; presumably on an unengaged `optional<T&>`, you get an exception.

Boost provides one answer, but what makes it the right answer? There are plenty of people on both sides who have valid viewpoints, and regardless of which side you pick, someone is going to get it wrong.

However, if you create some alternative `select` type, it could by design not be rebindable/assignable. Even for value types. So when you create it, you either provide a value/reference, and the `select<T>` will forever either be engaged or not engaged. The binding is immutable. If you move-construct from a `select<T>`, it will not leave the former object unengaged; it will merely move from the engaged value.

After all, look at this `variant` case. We don't need the return value of such a function to have mutable bindings. It's perfectly fine for our needs if that object is forever associated with the `variant`.

Armed with such a type, we no longer have to deal with the questions of `optional<T&>`'s assignment behavior. It might be cleaner to only have one type, yes. But since `optional` is expected to have mutable bindings, and that expectation does not play nice with references, it is best to have an alternative object for cases where bindings don't need to be mutable.

Vicente J. Botet Escriba

unread,
May 18, 2018, 8:21:12 AM5/18/18
to std-pr...@isocpp.org, torto...@gmail.com
Le 17/05/2018 à 12:30, torto...@gmail.com a écrit :
On Tuesday, 15 May 2018 22:34:36 UTC+1, Vicente J. Botet Escriba wrote:
Le 15/05/2018 à 17:39, torto...@gmail.com a écrit :
>     I'm wondering if there is, or should be a standard way to convert
> a variant into an optional?
> Or if its a bad idea why there shouldn't be.
>

>
I very much like the name select.

I see your argument that a ValueOrNone isn't strictly an optional but if it isn't don't we complicate the language
with yet another "maybe" type? Wouldn't the language be cleaner with just one?
I guess the answer is adding optional references as previously mentioned?

Well, with the select function, you don't need to do any conversion, even not to optional<T&>. Note that the type obtained with select, can be an implementation detail. We don't have to name it. And of course, it can be convertible to optional<T> or optional<T&> if we had it.

Vicente

P.S. I've a variadic select that applied to std::any results in a sum type that is equivalent to a nullable variant.


std::any a;
auto v = select<T1, ..., Tn>(a); // equivalent to variant<none_t, T1&, ..., Tn&>

Once we have done that, we can visit a std::any :)
The problem is that the complexity is not O(1) but O(n) :(


Reply all
Reply to author
Forward
0 new messages