基于对函数的参数调用流输出运算符的能力重载函数

Overloading a function based on the ability to call the stream output operator on its arguments

本文关键字:函数 输出 运算符 重载 能力 参数 调用      更新时间:2023-10-16

我有一个泛型函数,它接受两个参数,比较它们,如果它们不相等,则打印一条消息。现在,我只有这个相对愚蠢的功能:

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;
}
}

这编译了,但它没有被选中Tintstd::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(1int)和T = long(2llong)之间进行选择。

如果您重写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;
}

前面的调用编译是因为T1int推导,T2推导long

如果您根据T1启用AreEqual()的一个版本或另一个版本T2则(可能)有四种情况(T1T2可打印的;T1可打印的,T2不是;T2可打印的,T1不是;T1T2不可打印)所以四个版本的AreEqual().

通过maybePrint()您可以维护单个AreEqual()