下面是一个使用Boost Test的简单程序,它的行为"奇怪":
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE foo
#include <boost/test/unit_test.hpp>
class C
{
public:
C(char* str) : m_str(str) {}
operator char*() const { return m_str; }
char* get() const { return m_str; }
private:
char* m_str;
};
BOOST_AUTO_TEST_CASE(test1)
{
char s1[] = "hello";
char s2[] = "hello";
C c1(s1);
C c2(s2);
BOOST_CHECK_EQUAL(s1, s2); // check 1: passes
BOOST_CHECK_EQUAL(c1, c2); // check 2: fails (why?)
BOOST_CHECK_EQUAL(c1.get(), c2.get()); // check 3: passes
}
如果你运行这个,它会在比较c1和c2时报告一个失败,当它看起来应该通过时。原因是Boost Test中的这段代码(我使用的是1.51):
// this is called for check 2
template <class Left, class Right>
predicate_result equal_impl( Left const& left, Right const& right )
{
return left == right;
}
// this is called for checks 1 and 3
predicate_result BOOST_TEST_DECL equal_impl( char const* left, char const* right );
inline predicate_result equal_impl( char* left, char* right ) { return equal_impl( static_cast<char const*>(left), static_cast<char const*>(right) ); }
// this decides which comparator to call
struct equal_impl_frwd {
// this is called for checks 2 and 3
template <typename Left, typename Right>
inline predicate_result
call_impl( Left const& left, Right const& right, mpl::false_ ) const
{
return equal_impl( left, right );
}
// this is called for check 1
template <typename Left, typename Right>
inline predicate_result
call_impl( Left const& left, Right const& right, mpl::true_ ) const
{
return (*this)( right, &left[0] );
}
template <typename Left, typename Right>
inline predicate_result
operator()( Left const& left, Right const& right ) const
{
typedef typename is_array<Left>::type left_is_array;
return call_impl( left, right, left_is_array() );
}
};
因此,首先,BOOST _CHECK_EQUAL在编译时决定参数是否为数组。在检查1中,它们是,并且数组降级为指针。然后它决定如何比较这些论点。如果参数的类型是char*,它会将它们作为C字符串进行比较。否则,它将使用运算符==。所以问题是类C不是char*,所以检查2是使用运算符==完成的。但是C类没有运算符==,所以编译器决定隐式地将c1和c2转换为char*,在这一点上定义了运算符==但将它们作为地址而不是C字符串进行比较。
因此,我们最终遇到了一个相当奇怪的情况:Boost Test打算总是将char*参数作为C字符串进行比较,但它不知道比较c1和c2的唯一方法是将它们转换为char*。
我的问题是,我们如何才能在这里做得更好?例如,有没有一种方法可以在编译时理解,当为c1和c2调用运算符==时,它将使用它们到char*的隐式转换?这有点像在编译时使用decltype()来计算表达式的返回类型,只是我们需要计算表达式的参数类型(即c1 == c2
)。
我认为你不能真正支持这种场景,因为我能想到的所有SFINAE技术都会遇到模糊的重载。
事实上,这正是Boost的has_equal_to<A,B,R>
类型特征的限制:
如果运算符仅存在于类型A,并且B可转换为A,则会出现问题。在这种情况下,编译器将报告不明确的重载。