我认为你过度设计了这个。我建议一个简单的宏,也许有一个合适的朋友定义。也就是说,让我们接受挑战吧。
必要的调整
你的类型应该
- 对于
check_is_close_t 实现是可默认构造的。
- 另外,必须有一种获取值的方法,并且由于您拒绝创建 getter,剩下的唯一选择是将访问器类声明为朋友
我们得到
class MyTypeWithDouble
{
public:
constexpr MyTypeWithDouble(double v = 0) : m(v) {}
MyTypeWithDouble& operator=(MyTypeWithDouble const&) noexcept = default;
constexpr MyTypeWithDouble(MyTypeWithDouble const&) noexcept = default;
private:
friend class unittest_op::access;
double m;
};
通过一些繁琐的工作(在标题中?)我们可以使用这个access 漏洞来实现其他所有内容。 如何?好吧,我们定义了一个“getter”,好吧,但是在类定义之外。
我在access 中定义了一个特征类模板(因此它隐含为friend),您可以专门针对您的“类浮点”类型:
namespace unittest_op {
template<> class access::impl<MyTypeWithDouble> {
public:
typedef double result_type;
static result_type call(MyTypeWithDouble const& v) { return v.m; }
};
}
就是这样。好吧,这就是你作为类型/测试实现者的全部内容。当然,我们仍然需要完成这项工作。
具体细节
unittest_op 命名空间存在的唯一原因是定义“中继”运算符,这些运算符知道如何访问自定义类型中包含的值。
注意我们的方式
- 不需要向您的用户定义类型添加任何内容
- 我们也会得到混合操作数(例如
2 * MyTypeWithDouble(7.0) -> MyTypeWithDouble(14.0))
- 我们还定义了
operator<<,因此断言宏知道如何打印MyTypeWithDouble 的值
感谢c++11 工作并不复杂:
namespace unittest_op {
class access {
template<typename T, typename Enable = void> class impl;
template<typename T>
class impl<T, typename std::enable_if<std::is_arithmetic<T>::value, void>::type>
{
public: typedef T result_type;
static T & call(T& v) { return v; }
static T const& call(T const& v) { return v; }
};
public:
template<typename T>
static typename impl<T>::result_type do_access(T const& v) { return impl<T>::call(v); }
template<typename T> static constexpr bool can_access(decltype(do_access(std::declval<T>()))*) { return true; }
template<typename T> static constexpr bool can_access(...) { return false; }
};
template<typename T>
typename std::enable_if<access::can_access<T>(nullptr) && not std::is_arithmetic<T>::value, std::ostream&>::type
operator<<(std::ostream& os, T const& v) { return os << "{" << access::do_access(v) << "}"; }
template <typename T, typename Enable=decltype(access::do_access(std::declval<T>())) >
static T operator-(T const& lhs) { return - access::do_access(lhs); }
template <typename T, typename Enable=decltype(access::do_access(std::declval<T>())) >
static T operator+(T const& lhs) { return + access::do_access(lhs); }
#define UNITTEST_OP_BINOP(OP) \
template <typename T1, typename T2> \
static decltype(access::do_access(std::declval<T1>()) OP access::do_access(std::declval<T2>())) \
operator OP(T1 const& lhs, T2 const& rhs) { return access::do_access(lhs) OP access::do_access(rhs); } \
using ::unittest_op::operator OP;
UNITTEST_OP_BINOP(==)
UNITTEST_OP_BINOP(!=)
UNITTEST_OP_BINOP(< )
UNITTEST_OP_BINOP(> )
UNITTEST_OP_BINOP(<=)
UNITTEST_OP_BINOP(>=)
UNITTEST_OP_BINOP(+ )
UNITTEST_OP_BINOP(- )
UNITTEST_OP_BINOP(% )
UNITTEST_OP_BINOP(* )
UNITTEST_OP_BINOP(/ )
// assign-ops only for lvalue types (i.e. identity `access::impl<T>`)
UNITTEST_OP_BINOP(+=)
UNITTEST_OP_BINOP(-=)
UNITTEST_OP_BINOP(%=)
UNITTEST_OP_BINOP(*=)
UNITTEST_OP_BINOP(/=)
#undef UNITTEST_OP_BINOP
}
请注意,这些都是“开放”模板,我们已采取必要的预防措施,以确保这些运算符仅适用,如果 do_access 已定义和该类型一开始就不是算术类型。
为什么要采取这些预防措施?
嗯。我们要做一个强力动作:我们要将运算符重载注入boost::test_tools 命名空间,以便BOOST_CHECK* 宏实现可以找到它们。
如果我们没有采取刚才提到的预防措施,我们会因为我们不关心的类型的模棱两可的运算符重载而引发很多问题。
权力争夺
权力争夺很简单:我们将 (using) 我们的每个运算符模板注入到 boost::test_tools 命名空间中。
现在我们可以开始了:
Live On Coliru
BOOST_AUTO_TEST_CASE(my_test)
{
MyTypeWithDouble v(4);
BOOST_CHECK_CLOSE(3.99, v, MyTypeWithDouble(0.1));
}
打印
Running 2 test cases...
main.cpp(117): error in "my_test": difference{0.25%} between 3.99{3.9900000000000002} and v{{4}} exceeds {0.10000000000000001}%
完整程序
Live On Coliru
#include <utility>
#include <type_traits>
#include <iostream>
namespace unittest_op {
class access {
template<typename T, typename Enable = void> class impl;
template<typename T>
class impl<T, typename std::enable_if<std::is_arithmetic<T>::value, void>::type>
{
public: typedef T result_type;
static T & call(T& v) { return v; }
static T const& call(T const& v) { return v; }
};
public:
template<typename T>
static typename impl<T>::result_type do_access(T const& v) { return impl<T>::call(v); }
template<typename T> static constexpr bool can_access(decltype(do_access(std::declval<T>()))*) { return true; }
template<typename T> static constexpr bool can_access(...) { return false; }
};
template<typename T>
typename std::enable_if<access::can_access<T>(nullptr) && not std::is_arithmetic<T>::value, std::ostream&>::type
operator<<(std::ostream& os, T const& v) { return os << "{" << access::do_access(v) << "}"; }
template <typename T, typename Enable=decltype(access::do_access(std::declval<T>())) >
static T operator-(T const& lhs) { return - access::do_access(lhs); }
template <typename T, typename Enable=decltype(access::do_access(std::declval<T>())) >
static T operator+(T const& lhs) { return + access::do_access(lhs); }
#define UNITTEST_OP_BINOP(OP) \
template <typename T1, typename T2> \
static decltype(access::do_access(std::declval<T1>()) OP access::do_access(std::declval<T2>())) \
operator OP(T1 const& lhs, T2 const& rhs) { return access::do_access(lhs) OP access::do_access(rhs); } \
using ::unittest_op::operator OP;
UNITTEST_OP_BINOP(==)
UNITTEST_OP_BINOP(!=)
UNITTEST_OP_BINOP(< )
UNITTEST_OP_BINOP(> )
UNITTEST_OP_BINOP(<=)
UNITTEST_OP_BINOP(>=)
UNITTEST_OP_BINOP(+ )
UNITTEST_OP_BINOP(- )
UNITTEST_OP_BINOP(% )
UNITTEST_OP_BINOP(* )
UNITTEST_OP_BINOP(/ )
// assign-ops only for lvalue types (i.e. identity `access::impl<T>`)
UNITTEST_OP_BINOP(+=)
UNITTEST_OP_BINOP(-=)
UNITTEST_OP_BINOP(%=)
UNITTEST_OP_BINOP(*=)
UNITTEST_OP_BINOP(/=)
#undef UNITTEST_OP_BINOP
}
namespace boost { namespace test_tools {
using unittest_op::operator ==;
using unittest_op::operator !=;
using unittest_op::operator < ;
using unittest_op::operator > ;
using unittest_op::operator <=;
using unittest_op::operator >=;
using unittest_op::operator + ;
using unittest_op::operator - ;
using unittest_op::operator % ;
using unittest_op::operator * ;
using unittest_op::operator / ;
using unittest_op::operator +=;
using unittest_op::operator -=;
using unittest_op::operator %=;
using unittest_op::operator *=;
using unittest_op::operator /=;
using unittest_op::operator <<;
} }
class MyTypeWithDouble
{
public:
constexpr MyTypeWithDouble(double v = 0) : m(v) {}
MyTypeWithDouble& operator=(MyTypeWithDouble const&) noexcept = default;
constexpr MyTypeWithDouble(MyTypeWithDouble const&) noexcept = default;
private:
friend class unittest_op::access;
double m;
};
namespace unittest_op {
template<> class access::impl<MyTypeWithDouble> {
public:
typedef double result_type;
static result_type call(MyTypeWithDouble const& v) { return v.m; }
};
}
#define BOOST_TEST_MODULE MyTest
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_CASE(my_test)
{
MyTypeWithDouble v(4);
BOOST_CHECK_CLOSE(3.99, v, MyTypeWithDouble(0.1));
}
BOOST_AUTO_TEST_CASE(general_operator_invocations) // just a testbed to see the overloads are found and compile
{
MyTypeWithDouble v(4);
using namespace unittest_op; // we're not using the test_tools here
BOOST_CHECK(4.00000000000000001 == v);
BOOST_CHECK(4.000000000000001 != v);
#define UNITTEST_OP_BINOP(OP) { \
auto x = v OP static_cast<MyTypeWithDouble>(0.01); \
x = static_cast<MyTypeWithDouble>(0.01) OP v; \
x = v OP v; \
(void) x; \
}
UNITTEST_OP_BINOP(==)
UNITTEST_OP_BINOP(!=)
UNITTEST_OP_BINOP(+ )
UNITTEST_OP_BINOP(- )
//UNITTEST_OP_BINOP(% )
UNITTEST_OP_BINOP(* )
UNITTEST_OP_BINOP(/ )
UNITTEST_OP_BINOP(< )
UNITTEST_OP_BINOP(> )
UNITTEST_OP_BINOP(<=)
UNITTEST_OP_BINOP(>=)
-v == -v;
+v == +v;
}