Skip to content

Commit

Permalink
REQUIRE does not compile when operator== in different namespace #443 . (
Browse files Browse the repository at this point in the history
#468)

* REQUIRE does not compile when operator== in different namespace #443 .
Expression_lhs.op member method is not instantiated when it is missing
a member operator and the user defined conversion is able to apply the
global operator.

* Removing utility and using an overloaded version of declval which is faster in doctest_fwd.h .

* Using templated operator== inside TEST_CASE changes deduced types of forwarding references #399 . This is fixed by using rvalues as function argument and using forward for the right type of reference. Now both gcc and doctest either fails or either compiles but not like one compiles and the other fails
  • Loading branch information
navinp0304 committed Mar 21, 2021
1 parent b400ca9 commit 01546b2
Show file tree
Hide file tree
Showing 14 changed files with 438 additions and 14 deletions.
50 changes: 44 additions & 6 deletions doctest/doctest.h
Expand Up @@ -444,7 +444,35 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP
#include <type_traits>
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS



namespace doctest {
template<typename T, typename U = T&&> U declval(int);

template<typename T> T declval(long);

template<typename T> auto declval() noexcept -> decltype(declval<T>(0)) ;

template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};

template<class T> struct is_lvalue_reference { const static bool value=false; };
template<class T> struct is_lvalue_reference<T&> { const static bool value=true; };

template <class T>
inline T&& forward(typename remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t);
}

template <class T>
inline T&& forward(typename remove_reference<T>::type&& t) noexcept
{
static_assert(!is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}

DOCTEST_INTERFACE extern bool is_running_in_test;

Expand Down Expand Up @@ -1048,10 +1076,16 @@ namespace detail {
return toString(lhs) + op + toString(rhs);
}

// This will check if there is any way it could find a operator like member or friend and uses it.
// If not it doesn't find the operator or if the operator at global scope is defined after
// this template, the template won't be instantiated due to SFINAE. Once the template is not
// instantiated it can look for global operator using normal conversions.
#define SFINAE_OP(ret,op) decltype(doctest::declval<L>() op doctest::declval<R>(),static_cast<ret>(0))

#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \
template <typename R> \
DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \
bool res = op_macro(lhs, rhs); \
DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \
bool res = op_macro(doctest::forward<L>(lhs), doctest::forward<R>(rhs)); \
if(m_at & assertType::is_false) \
res = !res; \
if(!res || doctest::getContextOptions()->success) \
Expand Down Expand Up @@ -1179,8 +1213,8 @@ namespace detail {
L lhs;
assertType::Enum m_at;

explicit Expression_lhs(L in, assertType::Enum at)
: lhs(in)
explicit Expression_lhs(L&& in, assertType::Enum at)
: lhs(doctest::forward<L>(in))
, m_at(at) {}

DOCTEST_NOINLINE operator Result() {
Expand All @@ -1193,6 +1227,10 @@ namespace detail {
return Result(res);
}

/* This is required for user-defined conversions from Expression_lhs to L */
//operator L() const { return lhs; }
operator L() const { return lhs; }

// clang-format off
DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional
DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional
Expand Down Expand Up @@ -1244,8 +1282,8 @@ namespace detail {
// https://github.com/catchorg/Catch2/issues/870
// https://github.com/catchorg/Catch2/issues/565
template <typename L>
Expression_lhs<const DOCTEST_REF_WRAP(L)> operator<<(const DOCTEST_REF_WRAP(L) operand) {
return Expression_lhs<const DOCTEST_REF_WRAP(L)>(operand, m_at);
Expression_lhs<L> operator<<(L &&operand) {
return Expression_lhs<L>(doctest::forward<L>(operand), m_at);
}
};

Expand Down
50 changes: 44 additions & 6 deletions doctest/parts/doctest_fwd.h
Expand Up @@ -441,7 +441,35 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP
#include <type_traits>
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS



namespace doctest {
template<typename T, typename U = T&&> U declval(int);

template<typename T> T declval(long);

template<typename T> auto declval() noexcept -> decltype(declval<T>(0)) ;

template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};

template<class T> struct is_lvalue_reference { const static bool value=false; };
template<class T> struct is_lvalue_reference<T&> { const static bool value=true; };

template <class T>
inline T&& forward(typename remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t);
}

template <class T>
inline T&& forward(typename remove_reference<T>::type&& t) noexcept
{
static_assert(!is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}

DOCTEST_INTERFACE extern bool is_running_in_test;

Expand Down Expand Up @@ -1045,10 +1073,16 @@ namespace detail {
return toString(lhs) + op + toString(rhs);
}

// This will check if there is any way it could find a operator like member or friend and uses it.
// If not it doesn't find the operator or if the operator at global scope is defined after
// this template, the template won't be instantiated due to SFINAE. Once the template is not
// instantiated it can look for global operator using normal conversions.
#define SFINAE_OP(ret,op) decltype(doctest::declval<L>() op doctest::declval<R>(),static_cast<ret>(0))

#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \
template <typename R> \
DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \
bool res = op_macro(lhs, rhs); \
DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \
bool res = op_macro(doctest::forward<L>(lhs), doctest::forward<R>(rhs)); \
if(m_at & assertType::is_false) \
res = !res; \
if(!res || doctest::getContextOptions()->success) \
Expand Down Expand Up @@ -1176,8 +1210,8 @@ namespace detail {
L lhs;
assertType::Enum m_at;

explicit Expression_lhs(L in, assertType::Enum at)
: lhs(in)
explicit Expression_lhs(L&& in, assertType::Enum at)
: lhs(doctest::forward<L>(in))
, m_at(at) {}

DOCTEST_NOINLINE operator Result() {
Expand All @@ -1190,6 +1224,10 @@ namespace detail {
return Result(res);
}

/* This is required for user-defined conversions from Expression_lhs to L */
//operator L() const { return lhs; }
operator L() const { return lhs; }

// clang-format off
DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional
DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional
Expand Down Expand Up @@ -1241,8 +1279,8 @@ namespace detail {
// https://github.com/catchorg/Catch2/issues/870
// https://github.com/catchorg/Catch2/issues/565
template <typename L>
Expression_lhs<const DOCTEST_REF_WRAP(L)> operator<<(const DOCTEST_REF_WRAP(L) operand) {
return Expression_lhs<const DOCTEST_REF_WRAP(L)>(operand, m_at);
Expression_lhs<L> operator<<(L &&operand) {
return Expression_lhs<L>(doctest::forward<L>(operand), m_at);
}
};

Expand Down
19 changes: 19 additions & 0 deletions examples/all_features/CMakeLists.txt
Expand Up @@ -22,6 +22,15 @@ set(files_all
${files_with_output}
concurrency.cpp
../../scripts/coverage_maxout.cpp
namespace1.cpp
namespace2.cpp
namespace3.cpp
namespace4.cpp
namespace5.cpp
namespace6.cpp
namespace7.cpp
namespace8.cpp
namespace9.cpp
)

# add the executable
Expand Down Expand Up @@ -49,6 +58,16 @@ if(NOT MINGW AND NOT DEFINED DOCTEST_THREAD_LOCAL)
doctest_add_test(NO_OUTPUT NAME concurrency.cpp ${common_args} -sf=*concurrency.cpp -d) # duration: there is no output anyway
endif()

doctest_add_test(NO_OUTPUT NAME namespace1.cpp ${common_args} -sf=*namespace1.cpp )
doctest_add_test(NO_OUTPUT NAME namespace2.cpp ${common_args} -sf=*namespace2.cpp )
doctest_add_test(NO_OUTPUT NAME namespace3.cpp ${common_args} -sf=*namespace3.cpp )
doctest_add_test(NO_OUTPUT NAME namespace4.cpp ${common_args} -sf=*namespace4.cpp )
doctest_add_test(NO_OUTPUT NAME namespace5.cpp ${common_args} -sf=*namespace5.cpp )
doctest_add_test(NO_OUTPUT NAME namespace6.cpp ${common_args} -sf=*namespace6.cpp )
doctest_add_test(NO_OUTPUT NAME namespace7.cpp ${common_args} -sf=*namespace7.cpp )
doctest_add_test(NO_OUTPUT NAME namespace8.cpp ${common_args} -sf=*namespace8.cpp )
doctest_add_test(NO_OUTPUT NAME namespace9.cpp ${common_args} -sf=*namespace9.cpp )

# add this separately since the file has a non-straightforward path
doctest_add_test(NAME coverage_maxout.cpp ${common_args} -sf=*coverage_maxout.cpp)

Expand Down
27 changes: 27 additions & 0 deletions examples/all_features/namespace1.cpp
@@ -0,0 +1,27 @@
#include <doctest/doctest.h>

DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <cstdint>
#include <sstream>
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END

namespace user1 {
struct label
{
label()
: i(0) {}
int i;
};
} // namespace user1

DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")

bool operator==(const user1::label& lhs, const user1::label& rhs) { return lhs.i == rhs.i; }


TEST_CASE("namespace 1 global operator") {
user1::label a;
user1::label b;
CHECK(a == b);
}
24 changes: 24 additions & 0 deletions examples/all_features/namespace2.cpp
@@ -0,0 +1,24 @@
#include <doctest/doctest.h>

DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <cstdint>
#include <sstream>
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END

namespace user2 {
struct label
{
label()
: i(0) {}
int i;
friend bool operator==(const user2::label& lhs, const user2::label& rhs) {
return lhs.i == rhs.i;
}
};
} // namespace user2

TEST_CASE("namespace 2 friend operator") {
user2::label a;
user2::label b;
REQUIRE(a == b);
}
22 changes: 22 additions & 0 deletions examples/all_features/namespace3.cpp
@@ -0,0 +1,22 @@
#include <doctest/doctest.h>

DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <cstdint>
#include <sstream>
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END

namespace user3 {
struct label
{
label()
: i(0) {}
int i;
bool operator==(const user3::label& rhs) const { return i == rhs.i; }
};
} // namespace user3

TEST_CASE("namespace 3 member operator") {
user3::label a;
user3::label b;
REQUIRE(a == b);
}
37 changes: 37 additions & 0 deletions examples/all_features/namespace4.cpp
@@ -0,0 +1,37 @@
#include <doctest/doctest.h>

DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <cstdint>
#include <sstream>
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END

namespace user4 {
struct label
{
label()
: i(0) {}
int i;
bool operator==(const user4::label& rhs) const { return i == rhs.i; }
};
} // namespace user4

namespace user5 {
struct label
{
label()
: i(0) {}
int i;
bool operator==(const user5::label& rhs) const { return i == rhs.i; }
};
} // namespace user5

TEST_CASE("namespace 4 member vs member") {
user4::label a4;
user4::label b4;

user5::label a5;
user5::label b5;

REQUIRE(a4 == b4);
REQUIRE(a5 == b5);
}
39 changes: 39 additions & 0 deletions examples/all_features/namespace5.cpp
@@ -0,0 +1,39 @@
#include <doctest/doctest.h>

DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <cstdint>
#include <sstream>
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END

namespace user6 {
struct label
{
label()
: i(0) {}
int i;
bool operator==(const user6::label& rhs) const { return i == rhs.i; }
};
} // namespace user6

namespace user7 {
struct label
{
label()
: i(0) {}
int i;
friend bool operator==(const user7::label& lhs, const user7::label& rhs) {
return lhs.i == rhs.i;
}
};
} // namespace user7

TEST_CASE("namespace 5 member vs friend") {
user6::label a6;
user6::label b6;

user7::label a7;
user7::label b7;

REQUIRE(a6 == b6);
REQUIRE(a7 == b7);
}

0 comments on commit 01546b2

Please sign in to comment.