Skip to content
Closed
1 change: 0 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ set(_boost_test_dependencies
Boost::optional
Boost::preprocessor
Boost::smart_ptr
Boost::static_assert
Boost::type_traits
Boost::utility
)
Expand Down
1 change: 0 additions & 1 deletion build.jam
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ constant boost_dependencies :
/boost/optional//boost_optional
/boost/preprocessor//boost_preprocessor
/boost/smart_ptr//boost_smart_ptr
/boost/static_assert//boost_static_assert
/boost/type_traits//boost_type_traits
/boost/utility//boost_utility ;

Expand Down
4 changes: 3 additions & 1 deletion include/boost/test/impl/unit_test_main.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ unit_test_main( init_unit_test_func init_func, int argc, char* argv[] )

// getchar is defined as a macro in uClibc. Use parenthesis to fix
// gcc bug 58952 for gcc <= 4.8.2.
(std::getchar)();
int ch = (std::getchar)();
boost::ignore_unused(ch);

results_reporter::get_stream() << "Continuing..." << std::endl;
}

Expand Down
149 changes: 140 additions & 9 deletions include/boost/test/tools/assertion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
#include <utility>
#endif

// GCC < 10 workaround for std::optional comparison ambiguity (see below)
#if ((defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 10)) || (defined(__clang__) && __clang_major__ < 11)) && \
(__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))
#include <optional>
#endif

#include <boost/test/detail/suppress_warnings.hpp>

//____________________________________________________________________________//
Expand All @@ -38,6 +44,28 @@ namespace boost {
namespace test_tools {
namespace assertion {

// ************************************************************************** //
// ************** assertion::is_std_optional ************** //
// ************************************************************************** //
// Trait to detect std::optional. Used for GCC < 10 workaround.

template<typename T>
struct is_std_optional : std::false_type {};

#if ((defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 10)) || (defined(__clang__) && __clang_major__ < 11)) && \
(__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))

template<typename T>
struct is_std_optional<std::optional<T>> : std::true_type {};
template<typename T>
struct is_std_optional<std::optional<T>&> : std::true_type {};
template<typename T>
struct is_std_optional<std::optional<T> const&> : std::true_type {};
template<typename T>
struct is_std_optional<std::optional<T>&&> : std::true_type {};

#endif

// ************************************************************************** //
// ************** assertion::operators ************** //
// ************************************************************************** //
Expand Down Expand Up @@ -77,7 +105,8 @@ namespace op {

#ifndef BOOST_NO_CXX11_DECLTYPE

#define BOOST_TEST_FOR_EACH_CONST_OP(action)\
// Non-comparison operators (never need SFINAE workaround)
#define BOOST_TEST_FOR_EACH_NONCOMP_OP(action)\
action(->*, MEMP, ->*, MEMP ) \
\
action( * , MUL , * , MUL ) \
Expand All @@ -90,15 +119,20 @@ namespace op {
action( <<, LSH , << , LSH ) \
action( >>, RSH , >> , RSH ) \
\
BOOST_TEST_FOR_EACH_COMP_OP(action) \
\
action( & , BAND, & , BAND ) \
action( ^ , XOR , ^ , XOR ) \
action( | , BOR , | , BOR ) \
/**/

#define BOOST_TEST_FOR_EACH_CONST_OP(action)\
BOOST_TEST_FOR_EACH_NONCOMP_OP(action) \
BOOST_TEST_FOR_EACH_COMP_OP(action) \
/**/

#else

#define BOOST_TEST_FOR_EACH_NONCOMP_OP(action)

#define BOOST_TEST_FOR_EACH_CONST_OP(action)\
BOOST_TEST_FOR_EACH_COMP_OP(action) \
/**/
Expand Down Expand Up @@ -181,20 +215,87 @@ BOOST_TEST_FOR_EACH_CONST_OP( DEFINE_CONST_OPER )
} // namespace op

// ************************************************************************** //
// ************** assertion::expression_base ************** //
// ************** assertion::optional_friends_base ************** //
// ************************************************************************** //
// Defines expression operators
// GCC < 10 workaround: Base class that conditionally provides hidden friend
// comparison operators only when ValType is std::optional. Hidden friends are
// found via ADL and as non-templates beat std::optional's template operators.
// See: https://github.com/boostorg/test/issues/475

template<typename Lhs, typename Rhs, typename OP> class binary_expr;

#if ((defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 10)) || (defined(__clang__) && __clang_major__ < 11)) && \
(__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))

// Primary template - empty (for non-optional types)
template<typename ExprType, typename ValType, bool IsOptional = is_std_optional<ValType>::value>
struct optional_friends_base {};

// Specialization for std::optional types - provides hidden friend operators
template<typename ExprType, typename ValType>
struct optional_friends_base<ExprType, ValType, true> {
friend binary_expr<ExprType, ValType, op::EQ<ValType, ValType>>
operator==( ExprType lhs, ValType rhs )
{
return binary_expr<ExprType, ValType, op::EQ<ValType, ValType>>(
std::move(lhs), std::move(rhs));
}

friend binary_expr<ExprType, ValType, op::NE<ValType, ValType>>
operator!=( ExprType lhs, ValType rhs )
{
return binary_expr<ExprType, ValType, op::NE<ValType, ValType>>(
std::move(lhs), std::move(rhs));
}

friend binary_expr<ExprType, ValType, op::LT<ValType, ValType>>
operator<( ExprType lhs, ValType rhs )
{
return binary_expr<ExprType, ValType, op::LT<ValType, ValType>>(
std::move(lhs), std::move(rhs));
}

friend binary_expr<ExprType, ValType, op::LE<ValType, ValType>>
operator<=( ExprType lhs, ValType rhs )
{
return binary_expr<ExprType, ValType, op::LE<ValType, ValType>>(
std::move(lhs), std::move(rhs));
}

friend binary_expr<ExprType, ValType, op::GT<ValType, ValType>>
operator>( ExprType lhs, ValType rhs )
{
return binary_expr<ExprType, ValType, op::GT<ValType, ValType>>(
std::move(lhs), std::move(rhs));
}

friend binary_expr<ExprType, ValType, op::GE<ValType, ValType>>
operator>=( ExprType lhs, ValType rhs )
{
return binary_expr<ExprType, ValType, op::GE<ValType, ValType>>(
std::move(lhs), std::move(rhs));
}
};

#else // Not GCC < 10
template<typename ExprType, typename ValType, bool IsOptional = false>
struct optional_friends_base {};
#endif // GCC < 10 && C++17

// ************************************************************************** //
// ************** assertion::expression_base ************** //
// ************************************************************************** //
// Defines expression operators

template<typename ExprType,typename ValType>
class expression_base {
class expression_base : public optional_friends_base<ExprType, ValType> {
public:

#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
template<typename T>
struct RhsT : remove_const<typename remove_reference<T>::type> {};


// Regular operator support (non-comparison operators)
#define ADD_OP_SUPPORT( oper, name, _, _i ) \
template<typename T> \
binary_expr<ExprType,T, \
Expand All @@ -208,7 +309,33 @@ class expression_base {
std::forward<T>(rhs) ); \
} \
/**/

// GCC < 10 workaround: comparison operators with SFINAE to exclude std::optional
// when ValType is also std::optional. The hidden friend operators in the base class
// optional_friends_base will handle the optional==optional case instead.
#if ((defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 10)) || (defined(__clang__) && __clang_major__ < 11)) && \
(__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))
#define ADD_OP_SUPPORT_COMP( oper, name, _, _i ) \
template<typename T, \
typename std::enable_if< \
!is_std_optional<ValType>::value || \
!is_std_optional<typename RhsT<T>::type>::value, int>::type = 0> \
binary_expr<ExprType,T, \
op::name<ValType,typename RhsT<T>::type> > \
operator oper( T&& rhs ) \
{ \
return binary_expr<ExprType,T, \
op::name<ValType,typename RhsT<T>::type> > \
( std::forward<ExprType>( \
*static_cast<ExprType*>(this) ), \
std::forward<T>(rhs) ); \
} \
/**/
#else
#define ADD_OP_SUPPORT_COMP ADD_OP_SUPPORT
#endif

#else // BOOST_NO_CXX11_RVALUE_REFERENCES

#define ADD_OP_SUPPORT( oper, name, _, _i ) \
template<typename T> \
Expand All @@ -222,10 +349,14 @@ class expression_base {
rhs ); \
} \
/**/
#endif
#define ADD_OP_SUPPORT_COMP ADD_OP_SUPPORT

#endif // BOOST_NO_CXX11_RVALUE_REFERENCES

BOOST_TEST_FOR_EACH_CONST_OP( ADD_OP_SUPPORT )
BOOST_TEST_FOR_EACH_NONCOMP_OP( ADD_OP_SUPPORT )
BOOST_TEST_FOR_EACH_COMP_OP( ADD_OP_SUPPORT_COMP )
#undef ADD_OP_SUPPORT
#undef ADD_OP_SUPPORT_COMP

#ifndef BOOST_NO_CXX11_AUTO_DECLARATIONS
// Disabled operators
Expand Down
9 changes: 9 additions & 0 deletions include/boost/test/unit_test_suite.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@ typedef ::boost::unit_test::ut_detail::nil_t BOOST_AUTO_TEST_CASE_FIXTURE;
// ************** Auto registration facility helper macros ************** //
// ************************************************************************** //

#if defined(__clang__) && __clang_major__ >= 22
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wc2y-extensions"
#endif

// Facility for having a unique name based on __LINE__ and __COUNTER__ (later if available)
#if defined(__COUNTER__)
#define BOOST_TEST_INTERNAL_HAS_COUNTER
Expand All @@ -364,6 +369,10 @@ typedef ::boost::unit_test::ut_detail::nil_t BOOST_AUTO_TEST_CASE_FIXTURE;
#endif
/**/

#if defined(__clang__) && __clang_major__ >= 22
# pragma clang diagnostic pop
#endif

#define BOOST_AUTO_TU_REGISTRAR( test_name ) \
static boost::unit_test::ut_detail::auto_test_unit_registrar \
BOOST_TEST_APPEND_UNIQUE_ID( BOOST_JOIN( test_name, _registrar ) ) BOOST_ATTRIBUTE_UNUSED \
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ test-suite "writing-test-ts"
# [ boost.test-self-test run-fail : writing-test-ts : test-timeout-fail : : : : : : [ requires cxx11_hdr_thread cxx11_hdr_chrono ] ]
# [ boost.test-self-test run : writing-test-ts : test-timeout-suite : : : : : : $(requirements_datasets) [ requires cxx11_hdr_thread cxx11_hdr_chrono ] ]
# [ boost.test-self-test run-fail : writing-test-ts : test-timeout-suite-fail : : : : : : $(requirements_datasets) [ requires cxx11_hdr_thread cxx11_hdr_chrono ] ]
[ boost.test-self-test run : writing-test-ts : github_issue_475 : : : : : : [ requires cxx17_hdr_optional ] ]
;

#_________________________________________________________________________________________________#
Expand Down
1 change: 0 additions & 1 deletion test/cmake_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ else()
optional
preprocessor
smart_ptr
static_assert
type_traits
utility

Expand Down
15 changes: 15 additions & 0 deletions test/writing-test-ts/github_issue_475.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#define BOOST_TEST_MODULE github_issue_475
#include <optional>
#include <boost/test/unit_test.hpp>

BOOST_TEST_DONT_PRINT_LOG_VALUE(std::optional<int>)

BOOST_AUTO_TEST_CASE(test1)
{
std::optional<int> a,b;
BOOST_TEST(a==b);
}
Loading