我的代码中有一个情况,我想通过模板调用类定义的二元运算符重载,但第二个参数可能是 void 类型。可以写这个专业吗?
原因:
所以我有一个现有的宏化/模板包装,它可以帮助我记录函数的返回值。
它有点像这样:
#define Return return DebugPlacement({__FILE__,__LINE__}) <<=
Run Code Online (Sandbox Code Playgroud)
其中 DebugPlacement 是一个小点类,具有模板化重载operator <<=
struct DebugPlacement
{
const char* path; int line;
template <class Arg>
const Arg& operator <<= (const Arg& arg)const
{
std::cerr << "DIAG: " << path << ":" << line << " returns " << arg << std::endl;
return arg;
}
};
Run Code Online (Sandbox Code Playgroud)
我们选择运算符 <<= 是因为它相当晦涩,并且行为有点像现有的流运算符,但相反。而且它的低优先级意味着几乎任何合理的表达式都可以写在 RHS 上。
另一个妥协:所有类型通常都很简单,因此 rval 的使用不是一个大问题。我确信我们最终会在这里看到完美转发的需要,但现在还不是。
它打印该值并返回它。我可以插入各种一般专业化,因为我发现我需要它们, - char* 等。不需要额外的括号。效果非常好,谢谢您的询问!
在很多情况下,它用在 API 存根中:
extern int MyFunction(int (arg1), int (arg2));
int MyFunctionStub(int (arg1), int (arg2))
{
Return MyFunction(int (arg1), int (arg2));
}
Run Code Online (Sandbox Code Playgroud)
而这种存根行为反过来又被宏观化了。我知道——这是一种诅咒,也是一种祝福。不用说,我们的用例并不那么微不足道。存根也有工作要做,因此不能被消除或模板化。存根的名称在历史上也被固定为公共“C”API。
但如果返回类型为 void,一切都会崩溃!
看来善良的 c++ 大神认为以下内容是可以接受的,可能是因为模板包装器需要盲目转发返回类型:
extern void MyVFunction(int (arg1), int (arg2));
void MyVFunctionStub(int (arg1), int (arg2))
{
return MyVFunction(int (arg1), int (arg2));
}
Run Code Online (Sandbox Code Playgroud)
但通过宏替换,我无法添加跟踪。他们不喜欢:
extern void MyVFunction(int (arg1), int (arg2));
void MyVFunctionStub(int (arg1), int (arg2))
{
return DebugPlacement({__FILE__,__LINE__}) <<= MyVFunction(int (arg1), int (arg2));
}
Errors (at the call-site):
error: no viable overloaded '<<='
note: candidate template ignored: substitution failure [with T = void]: cannot form a reference to 'void'
Run Code Online (Sandbox Code Playgroud)
那么是否有某种形式的单词来声明需要右侧但类型为 void 的二元运算符的特化?目前我们仍然生活在c++11的土地上。难道我要等待后来的c++标准,还是标准化之神这次不眷顾我了?
当然,我也尝试过一些事情:
template <>
auto DebugPlacement::operator <<=(void& t)const -> void&
error: cannot form a reference to 'void'
Run Code Online (Sandbox Code Playgroud)
还
void operator <<=(void t)const
error: argument may not have 'void' type
Run Code Online (Sandbox Code Playgroud)
我的代码中有一个情况,我想通过模板调用类定义的二元运算符重载,但第二个参数可能是 void 类型。可以写这个专业吗?
您可以void使用内置的二元逗号运算符处理返回类型:
在逗号表达式中
E1, E2,表达式E1被求值,其结果被丢弃(尽管如果它具有类类型,则直到包含的完整表达式结束时它才会被销毁),并且其副作用在表达式求值E2开始之前完成(请注意,用户定义的运算符不能保证排序)(C++17 之前)。
当其右侧操作数为void逗号时,运算符的计算结果为void。逗号运算符只能对非空操作数重载。当任何操作数void仅调用内置逗号运算符时,operator,不考虑用户定义。
逗号运算符优先级最低,非常适合您的任务。
请注意,逗号符号 ( ,) 在不同的上下文中具有不同的含义:
各种逗号分隔列表(例如函数参数列表 (
f(a, b, c)) 和初始化列表int a[] = {1, 2, 3})中的逗号不是逗号运算符。如果需要在此类上下文中使用逗号运算符,则必须将其放在括号中:f(a, (n++, n + b), c)。
C++11 示例:
#include <iostream>
#include <utility>
struct ReturnValueLogger {
char const* file_;
int const line_;
std::ostream& log_file_line() const {
return std::clog << file_ << ":" << line_ << ' ';
}
ReturnValueLogger(ReturnValueLogger const&) = delete;
~ReturnValueLogger() {
if(file_) // operator, overload is not called for void rhs.
log_file_line() << "Return value is void\n";
}
template<class T>
auto operator,(T&& rhs) -> decltype(std::forward<T>(rhs)) { // Not called for void rhs.
log_file_line() << "Return value is " << rhs << '\n';
file_ = 0;
return std::forward<T>(rhs);
}
};
#define Return return ReturnValueLogger{__FILE__,__LINE__},
int f() { return 1; }
void g() {}
int f2() { Return f(); } // Outputs "Return value is 1".
void g2() { Return g(); } // Outputs "Return value is void".
int main() {
f2();
g2();
}
Run Code Online (Sandbox Code Playgroud)
重载逗号运算符对于类似的宏也很有用,Throw可以在抛出站点处使用堆栈跟踪、文件和行信息来检测异常。