Search code examples
c++c++17varianttype-erasure

Operator|| overloading for std::variant


Is it possible to overload operator|| for std::variant, use it if the alternative type has such operator and throw exception if alternative does not define such operator?

So far I got to something like:

template<typename ...Ts>
constexpr bool operator||(std::variant<Ts...> const& lhs, std::variant<Ts...> const& rhs)
{
    return /*no idea */;
}

Solution

  • First, use SFINAE to write a wrapper that calls the operator if possible or throw an exception otherwise:

    struct Invalid :std::exception { };
    
    struct Call_operator {
        template <typename T, typename U>
        constexpr auto operator()(T&& a, U&& b) const
            noexcept(std::is_nothrow_invocable_v<std::logical_or<>, T, U>)
            -> decltype(static_cast<bool>(std::declval<T>() || std::declval<U>()))
        {
            return std::forward<T>(a) || std::forward<U>(b);
        }
    
        [[noreturn]] bool operator()(...) const
        {
            throw Invalid{};
        }
    };
    

    Then, use visit, respecting noexcept:

    template <typename T, typename... Ts>
    struct is_nothrow_orable_impl
        :std::conjunction<std::is_nothrow_invocable<Call_operator, T, Ts>...> {};
    
    template <typename... Ts>
    struct is_nothrow_orable
        :std::conjunction<is_nothrow_orable_impl<Ts, Ts...>...> {};
    
    template<typename ...Ts>
    constexpr auto operator||(std::variant<Ts...> const& lhs, std::variant<Ts...> const& rhs)
        noexcept(is_nothrow_orable<Ts...>::value)
        -> decltype(std::visit(Call_operator{}, lhs, rhs))
    {
        return std::visit(Call_operator{}, lhs, rhs);
    }
    

    (live demo)