运算符||为std :: variant重载

时间:2019-07-30 08:53:05

标签: c++ c++17 variant type-erasure

是否有可能使operator||的{​​{1}}重载,如果替代类型具有这样的运算符,可以使用它,而如果替代类型没有定义此类运算符,则抛出异常?

到目前为止,我得到了类似的东西:

std::variant

2 个答案:

答案 0 :(得分:3)

首先,使用SFINAE编写一个包装程序,该包装程序将在可能的情况下调用运算符,否则将引发异常:

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{};
    }
};

然后,使用visit,遵守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

答案 1 :(得分:0)

通常人们不推荐overloading the operator || (或&&),因为您没有进行短路评估。

  

&&,||和,(逗号)在以下情况下失去其特殊的排序属性   重载并且表现得像常规函数调用,即使它们   不带函数调用符号使用。

Another approach将定义一个布尔转换运算符,正如我将在此处显示的那样。这需要一个类MyVariant而不是直接与std::variant一起工作。因此,此答案不能提供与问题中确切语法相同的解决方案。但是,我认为这种解决方案也可能很有趣。

来自@ L.F的(硬核)答案。我需要一些时间来理解,下面的代码使用了一个简单的布尔转换运算符和一个类似于{L.F的Call_constructor。然后可以使用运算符||&&,...。

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));
    }
};

用法

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;
}