1

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 */;
}
L. F.
  • 19,445
  • 8
  • 48
  • 82
Quest
  • 2,764
  • 1
  • 22
  • 44
  • 3
    maybe start with a free function instead of an operator and see how far you can get. I dont see any obvious obstacle. What did you try? Where is your code? – 463035818_is_not_an_ai Jul 30 '19 at 08:54
  • 6
    Since this operator short circuits, I highly recommend not overloading || and && for any types. – Tanveer Badar Jul 30 '19 at 09:00
  • 1
    Are you expecting `lhs` and `rhs` to contain the same alternative, or are you wanting e.g. `std::variant` to always produce a value? – Caleth Jul 30 '19 at 09:13
  • @Caleth the best case scenario would be bool and int produced a value but bool string would throw an exception for example – Quest Jul 30 '19 at 09:21
  • start with a `bool_or_except` that takes one `std::variant` – sp2danny Jul 30 '19 at 09:25

2 Answers2

3

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)

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • I was thinking something about `auto visitor = [](auto& l, auto &r) { if constexpr(boost::has_logical_or::value) { return l || r; }` – Quest Jul 30 '19 at 10:18
0

Often people do not recommend overloading the operator || (or &&) as you loose short circuit evaluation.

&&, ||, and , (comma) lose their special sequencing properties when overloaded and behave like regular function calls even when they are used without function-call notation.

Another approach would be to define a bool conversion operator, as I will show here. This requires a class MyVariant instead of working directly with std::variant. Therefore, this answer does not provide a solution with the exact syntax as in the question. However, I think this solution may be interesting as well.

Inspired from the (hard core) answer of @L.F. which I needed some time to understand, the below code uses a simple bool conversion operator and a Call_constructor similar to the one of @L.F. The operators ||, &&, ..., can then be used.

Call_operator

struct Call_Operator
{
    template <typename T>
    constexpr auto operator()(T&& a) const
        -> decltype(static_cast<bool>(std::declval<T>()))
    {
        return std::forward<T>(a);
    }

    bool operator()(...) const
    {
        throw std::exception();
    }

};

MyVariant

template <typename ... Args>
struct MyVariant : public std::variant<Args...>
{    
    explicit operator bool()
    {
        return std::visit(Call_Operator{}, static_cast<std::variant<Args...>>(*this));
    }
};

Usage

int main()
{
    struct C {}; // operator bool not defined -> if (C{}){} does not compile

    MyVariant<bool,int,char> v1 { 1 };
    MyVariant<float,C> v2 { C{} };

    if (v1) {} // no exception, returns true as static_cast<bool>(1) = true
    if (v2) {} // throw exception since an instance of C cannot be converted to bool
    if (v1 || v2) {} // no exception due to lazy evaluation (v2 is not evaluated as v1 returns true)
    if (v2 || v1) {} // throws exception (C cannot be converted to bool)
    if (v1 && v2) {} // throws exception ...

    return 0;
}
mfnx
  • 2,894
  • 1
  • 12
  • 28