Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload resolution for multiply inherited operator()

Tags:

First, consider this C++ code:

#include <stdio.h>  struct foo_int {     void print(int x) {         printf("int %d\n", x);     }     };  struct foo_str {     void print(const char* x) {         printf("str %s\n", x);     }     };  struct foo : foo_int, foo_str {     //using foo_int::print;     //using foo_str::print; };  int main() {     foo f;     f.print(123);     f.print("abc"); } 

As expected according to the Standard, this fails to compile, because print is considered separately in each base class for the purpose of overload resolution, and thus the calls are ambiguous. This is the case on Clang (4.0), gcc (6.3) and MSVC (17.0) - see godbolt results here.

Now consider the following snippet, the sole difference of which is that we use operator() instead of print:

#include <stdio.h>  struct foo_int {     void operator() (int x) {         printf("int %d\n", x);     }     };  struct foo_str {     void operator() (const char* x) {         printf("str %s\n", x);     }     };  struct foo : foo_int, foo_str {     //using foo_int::operator();     //using foo_str::operator(); };  int main() {     foo f;     f(123);     f("abc"); } 

I would expect the results to be identical to the previous case, but it is not the case - while gcc still complains, Clang and MSVC can compile this fine!

Question #1: who is correct in this case? I expect it to be gcc, but the fact that two other unrelated compilers give a consistently different result here makes me wonder whether I'm missing something in the Standard, and things are different for operators when they're not invoked using function syntax.

Also note that if you only uncomment one of the using declarations, but not the other, then all three compilers will fail to compile, because they will only consider the function brought in by using during overload resolution, and thus one of the calls will fail due to type mismatch. Remember this; we'll get back to it later.

Now consider the following code:

#include <stdio.h>  auto print_int = [](int x) {     printf("int %d\n", x); }; typedef decltype(print_int) foo_int;  auto print_str = [](const char* x) {     printf("str %s\n", x); }; typedef decltype(print_str) foo_str;  struct foo : foo_int, foo_str {     //using foo_int::operator();     //using foo_str::operator();     foo(): foo_int(print_int), foo_str(print_str) {} };  int main() {     foo f;     f(123);     f("foo"); } 

Again, same as before, except now we don't define operator() explicitly, but instead get it from a lambda type. Again, you'd expect the results to be consistent with the previous snippet; and this is true for the case where both using declarations are commented out, or if both are uncommented. But if you only comment out one but not the other, things are suddenly different again: now only MSVC complains as I would expect it to, while Clang and gcc both think it's fine - and use both inherited members for overload resolution, despite only one being brought in by using!

Question #2: who is correct in this case? Again, I'd expect it to be MSVC, but then why do both Clang and gcc disagree? And, more importantly, why this is different from the previous snippet? I would expect the lambda type to behave exactly the same as a manually defined type with overloaded operator()...

like image 672
Pavel Minaev Avatar asked Jun 08 '17 07:06

Pavel Minaev


People also ask

Can overloaded operators be inherited?

All overloaded operators except assignment (operator=) are inherited by derived classes.

How to overload operator in c++?

To overload an operator, we use a special operator function. We define the function inside the class or structure whose objects/variables we want the overloaded operator to work with. class className { ... .. ... public returnType operator symbol (arguments) { ... .. ... } ... .. ... };

Which operator is overloaded for cin operation?

Notes: The relational operators ( == , != , > , < , >= , <= ), + , << , >> are overloaded as non-member functions, where the left operand could be a non- string object (such as C-string, cin , cout ); while = , [] , += are overloaded as member functions where the left operand must be a string object.

Which operator can not be overloaded in c++ 1 point?

Dot (.) operator can't be overloaded, so it will generate an error.


1 Answers

Barry got #1 right. Your #2 hit a corner case: captureless nongeneric lambdas have an implicit conversion to function pointer, which got used in the mismatch case. That is, given

struct foo : foo_int, foo_str {     using foo_int::operator();     //using foo_str::operator();     foo(): foo_int(print_int), foo_str(print_str) {} } f;  using fptr_str = void(*)(const char*); 

f("hello") is equivalent to f.operator fptr_str()("hello"), converting the foo to an pointer-to-function and calling that. If you compile at -O0 you can actually see the call to the conversion function in the assembly before it gets optimized away. Put an init-capture in print_str, and you'll see an error since the implicit conversion goes away.

For more, see [over.call.object].

like image 147
T.C. Avatar answered Oct 01 '22 16:10

T.C.