Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Operator overload taking void

Tags:

c++

c++11

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
like image 689
Gem Taylor Avatar asked Apr 13 '26 19:04

Gem Taylor


1 Answers

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:

In a comma expression E1, E2, the expression E1 is evaluated, its result is discarded (although if it has class type, it won't be destroyed until the end of the containing full expression), and its side effects are completed before evaluation of the expression E2 begins (note that a user-defined operator, cannot guarantee sequencing) (until C++17).

When its right-hand operand is void comma operator evaluates to void. Comma operator can be overloaded only for non-void operands. When any operand is void only the built-in comma operator is invoked, the user-defined operator, is not considered.

Comma operator precedence is the lowest which is ideal for your task.

Note that comma symbol (,) has different meanings in different contexts:

The comma in various comma-separated lists, such as function argument lists (f(a, b, c)) and initializer lists int a[] = {1, 2, 3}, is not the comma operator. If the comma operator needs to be used in such contexts, it has to be parenthesized: f(a, (n++, n + b), c).

C++11 example:

#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();
}

Overloading comma operator is also useful for a similar macro Throw to instrument exceptions with stacktrace, file and line information at throw sites.

like image 124
Maxim Egorushkin Avatar answered Apr 15 '26 10:04

Maxim Egorushkin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!