diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0ce631..fac296d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: env: UBSAN_OPTIONS: print_stacktrace=1 + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true jobs: posix: diff --git a/doc/variant2/reference.adoc b/doc/variant2/reference.adoc index 59b9cb2..0ce2453 100644 --- a/doc/variant2/reference.adoc +++ b/doc/variant2/reference.adoc @@ -845,6 +845,9 @@ template [none] * {blank} + +Constraints: :: `std::declval() == std::declval()` is a valid expression + convertible to `bool` for all `Ti` in `T...`. ++ Returns: :: `v.index() == w.index() && get(v) == get(w)`, where `I` is `v.index()`. @@ -855,6 +858,9 @@ template [none] * {blank} + +Constraints: :: `std::declval() != std::declval()` is a valid expression + convertible to `bool` for all `Ti` in `T...`. ++ Returns: :: `!(v == w)`. ``` @@ -864,6 +870,9 @@ template [none] * {blank} + +Constraints: :: `std::declval() < std::declval()` is a valid expression + convertible to `bool` for all `Ti` in `T...`. ++ Returns: :: `v.index() < w.index() || (v.index() == w.index() && get(v) < get(w))`, where `I` is `v.index()`. @@ -874,6 +883,9 @@ template [none] * {blank} + +Constraints: :: `std::declval() < std::declval()` is a valid expression + convertible to `bool` for all `Ti` in `T...`. ++ Returns: :: `w < v`. ``` @@ -883,6 +895,9 @@ template [none] * {blank} + +Constraints: :: `std::declval() \<= std::declval()` is a valid expression + convertible to `bool` for all `Ti` in `T...`. ++ Returns: :: `v.index() < w.index() || (v.index() == w.index() && get(v) \<= get(w))`, where `I` is `v.index()`. @@ -893,6 +908,9 @@ template [none] * {blank} + +Constraints: :: `std::declval() \<= std::declval()` is a valid expression + convertible to `bool` for all `Ti` in `T...`. ++ Returns: :: `w \<= v`. diff --git a/include/boost/variant2/variant.hpp b/include/boost/variant2/variant.hpp index bb94c27..cff1c3e 100644 --- a/include/boost/variant2/variant.hpp +++ b/include/boost/variant2/variant.hpp @@ -2103,6 +2103,45 @@ template class variant: private detail::variant_ma_base namespace detail { +namespace relops_constraints +{ + +#if !defined(BOOST_NO_SFINAE_EXPR) && \ + !BOOST_WORKAROUND(BOOST_MSVC, < 1900) && !BOOST_WORKAROUND(BOOST_GCC, < 40900) + +template struct make_void { typedef void type; }; + +#define BOOST_VARIANT2_DEFINE_RELOP_CONSTRAINT( name, op ) \ +template struct name: std::false_type {}; \ +\ +template struct name() op std::declval() )>::type>: \ + std::is_convertible() op std::declval() ), bool> \ +{ \ +}; + +#else + +// non-expression-SFINAE fallback does not work with deleted relops: +// variant's relop instantiation fails instead, which is good enough + +struct not_comparable {}; +template not_comparable operator==( U const &, U const & ); +template not_comparable operator!=( U const &, U const & ); +template not_comparable operator< ( U const &, U const & ); +template not_comparable operator<=( U const &, U const & ); + +#define BOOST_VARIANT2_DEFINE_RELOP_CONSTRAINT( name, op ) \ +template struct name: std::is_convertible() op std::declval() ), bool> \ +{ \ +}; + +#endif + +BOOST_VARIANT2_DEFINE_RELOP_CONSTRAINT( has_eq, == ) + +} // namespace relops_constraints + template struct eq_L { variant const & v; @@ -2116,7 +2155,8 @@ template struct eq_L } // namespace detail -template constexpr bool operator==( variant const & v, variant const & w ) +template...>::value>::type> +constexpr bool operator==( variant const & v, variant const & w ) { return v.index() == w.index() && mp11::mp_with_index( v.index(), detail::eq_L{ v, w } ); } @@ -2124,6 +2164,13 @@ template constexpr bool operator==( variant const & v, variant namespace detail { +namespace relops_constraints +{ + +BOOST_VARIANT2_DEFINE_RELOP_CONSTRAINT( has_ne, != ) + +} // namespace relops_constraints + template struct ne_L { variant const & v; @@ -2137,7 +2184,8 @@ template struct ne_L } // namespace detail -template constexpr bool operator!=( variant const & v, variant const & w ) +template...>::value>::type> +constexpr bool operator!=( variant const & v, variant const & w ) { return v.index() != w.index() || mp11::mp_with_index( v.index(), detail::ne_L{ v, w } ); } @@ -2145,6 +2193,13 @@ template constexpr bool operator!=( variant const & v, variant namespace detail { +namespace relops_constraints +{ + +BOOST_VARIANT2_DEFINE_RELOP_CONSTRAINT( has_lt, < ) + +} // namespace relops_constraints + template struct lt_L { variant const & v; @@ -2158,12 +2213,14 @@ template struct lt_L } // namespace detail -template constexpr bool operator<( variant const & v, variant const & w ) +template...>::value>::type> +constexpr bool operator<( variant const & v, variant const & w ) { return v.index() < w.index() || ( v.index() == w.index() && mp11::mp_with_index( v.index(), detail::lt_L{ v, w } ) ); } -template constexpr bool operator>( variant const & v, variant const & w ) +template...>::value>::type> +constexpr bool operator>( variant const & v, variant const & w ) { return w < v; } @@ -2171,6 +2228,14 @@ template constexpr bool operator>( variant const & v, variant namespace detail { +namespace relops_constraints +{ + +BOOST_VARIANT2_DEFINE_RELOP_CONSTRAINT( has_le, <= ) +#undef BOOST_VARIANT2_DEFINE_RELOP_CONSTRAINT + +} // namespace relops_constraints + template struct le_L { variant const & v; @@ -2184,12 +2249,14 @@ template struct le_L } // namespace detail -template constexpr bool operator<=( variant const & v, variant const & w ) +template...>::value>::type> +constexpr bool operator<=( variant const & v, variant const & w ) { return v.index() < w.index() || ( v.index() == w.index() && mp11::mp_with_index( v.index(), detail::le_L{ v, w } ) ); } -template constexpr bool operator>=( variant const & v, variant const & w ) +template...>::value>::type> +constexpr bool operator>=( variant const & v, variant const & w ) { return w <= v; } diff --git a/test/Jamfile b/test/Jamfile index 4d88ebf..d8c3a77 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -180,3 +180,5 @@ run variant_issue_55.cpp # clang-cl 32 bit fails with an assertion in mp_with_index, likely due to a codegen bug "clang-win,32:no" ; + +run variant_constrained_relops.cpp ; diff --git a/test/variant_constrained_relops.cpp b/test/variant_constrained_relops.cpp new file mode 100644 index 0000000..ed3ad40 --- /dev/null +++ b/test/variant_constrained_relops.cpp @@ -0,0 +1,213 @@ + +// Copyright 2026 Joaquin M Lopez Munoz. +// +// Distributed under the Boost Software License, Version 1.0. +// +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include +#include + +#if !defined(BOOST_NO_SFINAE_EXPR) && \ + !BOOST_WORKAROUND(BOOST_MSVC, < 1900) && !BOOST_WORKAROUND(BOOST_GCC, < 40900) +#define USE_DELETED_RELOPS +#endif + +using namespace boost::variant2; + +struct not_found {}; +struct variant_sink { template variant_sink( variant const & ); }; +not_found operator==( variant_sink const &, variant_sink const & ); +not_found operator!=( variant_sink const &, variant_sink const & ); +not_found operator< ( variant_sink const &, variant_sink const & ); +not_found operator<=( variant_sink const &, variant_sink const & ); +not_found operator> ( variant_sink const &, variant_sink const & ); +not_found operator>=( variant_sink const &, variant_sink const & ); + +struct no_eq +{ +#ifdef USE_DELETED_RELOPS + bool operator==( const no_eq& ) const = delete; +#endif + int operator!=( const no_eq& ) const; + int operator< ( const no_eq& ) const; + int operator<=( const no_eq& ) const; +}; + +struct bad_eq +{ + void operator==( const bad_eq& ) const; + int operator!=( const bad_eq& ) const; + int operator< ( const bad_eq& ) const; + int operator<=( const bad_eq& ) const; +}; + +struct no_ne +{ + int operator==( const no_ne& ) const; +#ifdef USE_DELETED_RELOPS + bool operator!=( const no_ne& ) const = delete; +#endif + int operator< ( const no_ne& ) const; + int operator<=( const no_ne& ) const; +}; + +struct bad_ne +{ + int operator==( const bad_ne& ) const; + void operator!=( const bad_ne& ) const; + int operator< ( const bad_ne& ) const; + int operator<=( const bad_ne& ) const; +}; + +struct no_lt +{ + int operator==( const no_lt& ) const; + int operator!=( const no_lt& ) const; +#ifdef USE_DELETED_RELOPS + bool operator< ( const no_lt& ) const = delete; +#endif + int operator<=( const no_lt& ) const; +}; + +struct bad_lt +{ + int operator==( const bad_lt& ) const; + int operator!=( const bad_lt& ) const; + void operator< ( const bad_lt& ) const; + int operator<=( const bad_lt& ) const; +}; + +struct no_le +{ + int operator==( const no_le& ) const; + int operator!=( const no_le& ) const; + int operator< ( const no_le& ) const; +#ifdef USE_DELETED_RELOPS + bool operator<=( const no_le& ) const = delete; +#endif +}; + +struct bad_le +{ + int operator==( const bad_le& ) const; + int operator!=( const bad_le& ) const; + int operator< ( const bad_le& ) const; + void operator<=( const bad_le& ) const; +}; + +struct non_comparable {}; + +template +struct wrapper +{ + T t; + bool operator<(const wrapper& x) const { return t < x.t; }; +}; + +int main() +{ + { + using v_t = const variant&; + + BOOST_TEST_TRAIT_TRUE((std::is_same() == std::declval() ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() != std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() < std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() <= std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() > std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() >= std::declval() ), bool >)); + } + + { + using v_t = const variant&; + + BOOST_TEST_TRAIT_TRUE((std::is_same() == std::declval() ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() != std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() < std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() <= std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() > std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() >= std::declval() ), bool >)); + } + + { + using v_t = const variant&; + + BOOST_TEST_TRAIT_TRUE((std::is_same() == std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same(), std::declval() ) ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() < std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() <= std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() > std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() >= std::declval() ), bool >)); + } + + { + using v_t = const variant&; + + BOOST_TEST_TRAIT_TRUE((std::is_same() == std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same(), std::declval() ) ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() < std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() <= std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() > std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() >= std::declval() ), bool >)); + } + + { + using v_t = const variant&; + + BOOST_TEST_TRAIT_TRUE((std::is_same() == std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() != std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() < std::declval() ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() <= std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() > std::declval() ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() >= std::declval() ), bool >)); + } + + { + using v_t = const variant&; + + BOOST_TEST_TRAIT_TRUE((std::is_same() == std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() != std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() < std::declval() ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() <= std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() > std::declval() ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() >= std::declval() ), bool >)); + } + + { + using v_t = const variant&; + + BOOST_TEST_TRAIT_TRUE((std::is_same() == std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() != std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() < std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() <= std::declval() ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() > std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() >= std::declval() ), not_found>)); + } + + { + using v_t = const variant&; + + BOOST_TEST_TRAIT_TRUE((std::is_same() == std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() != std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() < std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() <= std::declval() ), not_found>)); + BOOST_TEST_TRAIT_TRUE((std::is_same() > std::declval() ), bool >)); + BOOST_TEST_TRAIT_TRUE((std::is_same() >= std::declval() ), not_found>)); + } + + { + //https://github.com/boostorg/variant2/issues/60 + + using v_t = variant; + using reference_wrapper = std::reference_wrapper; + + wrapper x{nullptr}, y{nullptr}; + (void)(x < y); + } + + return boost::report_errors(); +}