I have a case in my code where I want to call a class-defined binary operator overload through a template, but the second argument might be type void. Is it possible to write that specialisation?
The why:
So I have a piece of existing macroisation/template wrapping which helps me log return values from functions.
It goes a bit like this:
#define Return return DebugPlacement({__FILE__,__LINE__}) <<=
Where DebugPlacement is a little spot-class that has a templated overload for 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;
}
};
We chose operator <<= because it is pretty obscure and behaves somewhat like the existing streaming operator, but in reverse. Also its low precedence means that almost any sensible expression can be written on the RHS.
Another compromise: All the types are generally simple so use of rvals was not a big issue. I'm sure we will eventually see a need for perfect forwarding here, but not yet.
It prints the value, and returns it. And I can plug in various general specialisations as I find I need them, - char*, etc. No extra brackets are required. It works very nicely, thanks for asking!
In a lot of cases it is used in API stubs:
extern int MyFunction(int (arg1), int (arg2));
int MyFunctionStub(int (arg1), int (arg2))
{
Return MyFunction(int (arg1), int (arg2));
}
And that stub behaviour, in turn, has ended up macroised. I know - it is a curse and a blessing. Needless to say, our usecases are not quite this trivial. The stub does also have work to do, so can't be eliminated or templated away. The names of the stubs are also historically fixed as the public "C" API.
But it all falls apart if the return type is void!
It appears the nice c gods think the following is acceptable, probably because of template wrappers needing to blindly forward return types:
extern void MyVFunction(int (arg1), int (arg2));
void MyVFunctionStub(int (arg1), int (arg2))
{
return MyVFunction(int (arg1), int (arg2));
}
But with the macro substitution I can't add my tracing. They don't like:
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'
So is there some form of words to declare a specialisation of a binary operator that does require a right hand side, but of type void? Currently we are still living in a c 11 land. Am I going to have to wait for a later c standard, or are the standardisation gods not looking kindly upon me this time?
Of course, I have tried a few things:
template <>
auto DebugPlacement::operator <<=(void& t)const -> void&
error: cannot form a reference to 'void'
Also
void operator <<=(void t)const
error: argument may not have 'void' type
CodePudding user response:
I have a case in my code where I want to call a class-defined binary operator overload through a template, but the second argument might be type void. Is it possible to write that specialisation?
You can handle void
return type using built-in binary comma operator. When its right-hand operand is void
the operator evaluates to void
; can be overloaded for non-void operands. Comma operator precedence is the lowest which is ideal for your task.
C 11 example:
#include <iostream>
#include <utility>
struct ReturnValueLogger {
char const* file_;
int const line_;
ReturnValueLogger(ReturnValueLogger const&) = delete;
~ReturnValueLogger() {
if(file_) // operator, overload is not called for void rhs.
std::clog << file_ << ":" << line_ << " Return value is void\n";
}
template<class T>
auto operator,(T&& rhs) -> decltype(std::forward<T>(rhs)) { // Not called for void rhs.
std::clog << 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();
}
Overloading comma operator is also useful for a similar macro Throw
to instrument exceptions with stacktrace, file and line information at throw sites.