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()
...
All overloaded operators except assignment (operator=) are inherited by derived classes.
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) { ... .. ... } ... .. ... };
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.
Dot (.) operator can't be overloaded, so it will generate an error.
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].
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With