Boost logo

Boost :

Subject: Re: [boost] [variant2] documentation request
From: Ivan Matek (libbooze_at_[hidden])
Date: 2019-03-10 22:37:01


On Tue, Mar 5, 2019 at 11:05 PM Peter Dimov via Boost <boost_at_[hidden]>
wrote:

> Ivan Matek wrote:
>
> > > What happens with
> > >
> > > struct Bad
> > > {
> > > operator int() { throw false; }
> > > };
> > >
> > > variant<int, short> v = (short)10;
> > > v = Bad();
> >
> > I always considered this ugly corner case that prevents us from having
> > nice things.
>
> This is actually not a problem for variant2. When the contained types are
> trivial, as in this case, the exception, if any, occurs outside the
> variant.
> It has to, because otherwise constexpr can't work:
>
> https://godbolt.org/z/8kXcBN
>
>
Not sure that what this has to do with constexpr, so let me rephrase my
question in long but hopefully unambiguous way.

First let's forget about variant for a minute:
If I have an int of float or std::tuple<int, int, char> I can do anything
to instance of that type without any danger of exception( I am sure there
are some type_traits/concepts I should mention now, but IDK them by heart).
Other kind of types are types like std::string or std::forward_list where
some operations( string a, b="Toooo looooooong for SSO"; a *=* b;) might
throw.

Now when we move to variant of types where each T in list of variant types
is some POD(or what is the proper term these days?) I wonder if allowing
that variant to throw is a good idea or not? I suspect it is not, if
possible library should force user to move throwing stuff outside.
For example:
struct Bad
{
    int x;
    operator int() const
    {
        if( rand()%10==0) throw float(123.45);
        return x;
    }
};
variant2<int,short,float> v;
v=Bad{5};

I wonder if v=Bad{5};
should be banned by variant since all the Ts are no exception(by this I am
talking not about noexcept, but the fact you can do what you want to those
types and they will not throw) kind of types.

In other words if somebody wants to use variant<int, short, float> with Bad
he would need to write
v=int(Bad{5});

Now v can never be valuess_by_exception(unless I am missing some other ways
to corrupt it).

Stated differently I consider the behavior of std::variant unlucky corner
case of using perfect forwarding in operator = and emplace(since poor
variant ends up ingesting a potential bomb that will throw instead of
inspecting it at compile time so he knows it is safe), and I would like to
restrict the rhs of operator = in cases when all the types of variant are
types that never throw.
More specifically I would put some is_same checks here instead of
is_assignable(for cases when all types of variant are PODs, so you need a
std::conditional also):

    template<class U,
        class E1 = typename std::enable_if<!std::is_same<typename
std::decay<U>::type, variant>::value>::type,
        class V = detail::resolve_overload_type<U, T...>,
        class E2 = typename std::enable_if<std::is_assignable<V&,
U>::value && std::is_constructible<V, U>::value>::type
>
    BOOST_CXX14_CONSTEXPR variant& operator=( U&& u )
        noexcept( std::is_nothrow_assignable<V&, U>::value &&
std::is_nothrow_constructible<V, U>::value )
    {
        std::size_t const I = detail::resolve_overload_index<U, T...>::value;

        if( index() == I )
        {
            _get_impl( mp11::mp_size_t<I>() ) = std::forward<U>(u);
        }
        else
        {
            this->template emplace<I>( std::forward<U>(u) );
        }

        return *this;
    }

So
variant<int, short> v_simple;
v_simple = Bad{1}; // does not compile since we can guard against throws
inside variant(neither int or short throw)
v_simple = int(Bad{1}); // compiles
variant<int, std::string> v_complex;
v_complex = Bad{1}; // compiles since std::string operator = throws so we
can not guard against throws inside variant.

 regards,
Ivan


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk