我有一个泛型函数,它接受两个参数,比较它们,如果它们不相等,则打印一条消息。现在,我只有这个相对愚蠢的功能:
template <typename T>
static void AreEqual(const T& expected,
const T& actual,
const std::string& message = "") {
if (!(actual == expected)) {
std::cout << message;
}
}
多年来,这已经充分发挥作用。它最常与原语一起调用,但也用于比较较大的用户定义结构/类。
我想通过提供一个重载来扩展函数,当它们不匹配时打印预期值和实际值,但不破坏定义operator==
但不定义operator<<
的类的函数。我的想法是创建一个重载,如果缺少重载,则使用 SFINAE 禁用重载operator<<
。到目前为止,我已经想出了这个:
template <
typename T,
typename = typename std::enable_if_t<
std::is_same_v<decltype(std::cout << *((T*)nullptr)), decltype(std::cout)>>>
static void AreEqual(const T& expected,
const T& actual,
const std::string& message = "") {
if (!(actual == expected)) {
std::cout << "Expected " << expected << ", got " << actual << ". " << message;
}
}
这编译了,但它没有被选中T
int
或std::string
,我不确定为什么。我的第一个怀疑是我对is_same_v
的论点以某种方式是畸形的,但如果是这样的话,我不知道如何或如何弄清楚如何解决它。
问题1:所有这些甚至有必要吗?我可以在没有模板元编程的情况下获得相同的结果吗(最好在坚持使用 C++11 的同时)
问题 2:如果这是最好的方法,如何有效地调试我的模板?
你可以做这样的事情:
struct overload_low_priority {};
struct overload_high_priority : overload_low_priority {};
template <typename T>
static auto AreEqualImpl(const T& expected,
const T& actual,
const std::string& message,
overload_high_priority)
-> decltype(std::cout << expected, void()) // SFINAE
{
if (!(actual == expected)) {
std::cout << "Expected " << expected << ", got " << actual << ". " << message;
}
}
template <typename T>
static void AreEqualImpl(const T& expected,
const T& actual,
const std::string& message,
overload_low_priority) // Fallback
{
if (!(actual == expected)) {
std::cout << message;
}
}
template <typename T>
static void AreEqual(const T& expected,
const T& actual,
const std::string& message = "")
{
AreEqualImpl(expected, actual, message, overload_high_priority{});
}
我提出了一些不同的东西。
而不是std::cout
(或不expected
)值和actual
,你可以打印从值调用的函数返回的值。因此,您可以区分(重载)被调用函数的行为。
我的意思是。。。假设您编写了两个版本的函数maybePrint()
。
第一个是模板直通函数,仅当模板类型可打印时才启用 SFINAE
template <typename T>
auto maybePrint (T const & t) -> decltype( std::cout << t, t )
{ return t; }
第二个,当第一个不可用时调用(所以当参数不可打印时),返回一个信息丰富的字符串(嗯......也许选择一个更好的字符串)[编辑:根据Jarod42的报告修改]
template <typename ... Ts>
std::string maybePrint (Ts const & ...)
{ return "[maybe not]"; }
所以你的AreEqual()
变成
template <typename T>
static void AreEqual(const T& expected,
const T& actual,
const std::string& message = "")
{
if ( ! (actual == expected) )
std::cout << "Expected " << maybePrint(expected) << ", got "
<< maybePrint(actual) << ". " << message;
}
我建议此解决方案还因为,明天或遥远的将来,您可能会想修改AreEqual()
以区分模板类型。
这是因为以下调用
AreEqual(1, 2l, "abcn");
给出编译错误,因为编译器无法在T = int
(1
int
)和T = long
(2l
long
)之间进行选择。
如果您重写AreEqual()
收到(可能)两种不同类型的两个参数
template <typename T1, typename T2>
static void AreEqual (T1 const & expected,
T2 const & actual,
std::string const & message = "")
{
if ( ! (actual == expected) )
std::cout << "Expected " << maybePrint(expected) << ", got "
<< maybePrint(actual) << ". " << message;
}
前面的调用编译是因为T1
int
推导,T2
推导long
。
如果您根据T1
启用AreEqual()
的一个版本或另一个版本T2
则(可能)有四种情况(T1
和T2
可打印的;T1
可打印的,T2
不是;T2
可打印的,T1
不是;T1
和T2
不可打印)所以四个版本的AreEqual()
.
通过maybePrint()
您可以维护单个AreEqual()
。