运算符|| std::variant 超载
Operator|| overloading for std::variant
是否可以为 std::variant
重载 operator||
,如果替代类型有这样的运算符就使用它,如果替代类型没有定义这样的运算符则抛出异常?
到目前为止我得到了类似的东西:
template<typename ...Ts>
constexpr bool operator||(std::variant<Ts...> const& lhs, std::variant<Ts...> const& rhs)
{
return /*no idea */;
}
首先,使用 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);
}
通常人们不推荐overloading the operator || (或 &&) 当您松开短路评估时。
&&, ||, 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 将定义一个 bool 转换运算符,正如我将在此处展示的那样。这需要 class MyVariant
而不是直接使用 std::variant
。因此,此答案并未提供与问题中语法完全相同的解决方案。但是,我认为这个解决方案也可能很有趣。
灵感来自@L.F 的(硬核)答案。我需要一些时间来理解,下面的代码使用了一个简单的 bool 转换运算符和一个类似于 @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;
}
是否可以为 std::variant
重载 operator||
,如果替代类型有这样的运算符就使用它,如果替代类型没有定义这样的运算符则抛出异常?
到目前为止我得到了类似的东西:
template<typename ...Ts>
constexpr bool operator||(std::variant<Ts...> const& lhs, std::variant<Ts...> const& rhs)
{
return /*no idea */;
}
首先,使用 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);
}
通常人们不推荐overloading the operator || (或 &&) 当您松开短路评估时。
&&, ||, 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 将定义一个 bool 转换运算符,正如我将在此处展示的那样。这需要 class MyVariant
而不是直接使用 std::variant
。因此,此答案并未提供与问题中语法完全相同的解决方案。但是,我认为这个解决方案也可能很有趣。
灵感来自@L.F 的(硬核)答案。我需要一些时间来理解,下面的代码使用了一个简单的 bool 转换运算符和一个类似于 @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;
}