On Tue, Mar 5, 2019 at 11:05 PM Peter Dimov via Boost <boost@lists.boost.org> 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:
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