Сначала рассмотрим этот код на С++:
#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");
}
Как и ожидалось в соответствии со стандартом, это не скомпилируется, потому что print рассматривается отдельно в каждом базовом классе с целью разрешения перегрузки, и, следовательно, вызовы неоднозначны. Это относится к Clang (4.0), gcc (6.3) и MSVC (17.0) - см. Результаты godbolt здесь.
Теперь рассмотрим следующий фрагмент, единственная разница которого заключается в том, что вместо print мы используем operator():
#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");
}
Я ожидаю, что результаты будут идентичны предыдущему случаю, но это не тот случай - в то время как gcc все еще жалуется, Clang и MSVC может скомпилировать этот штраф!
Вопрос №1: кто прав в этом случае? Я ожидаю, что это будет gcc, но тот факт, что два других несвязанных компилятора дают неизменно другой результат, заставляет меня задаться вопросом, не хватает ли я чего-то в стандарте, и все по-другому для операторов, когда они не вызываются с помощью синтаксиса функций.
Также обратите внимание, что если вы только раскомментируете одну из объявлений using, но не другую, то все три компилятора не скомпилируются, потому что они будут рассматривать функцию, введенную using во время разрешения перегрузки, и таким образом, один из вызовов будет терпеть неудачу из-за несоответствия типов. Помните это; мы вернемся к нему позже.
Теперь рассмотрим следующий код:
#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");
}
Опять же, как и раньше, за исключением теперь мы не определяем operator() явно, а вместо этого получаем его из лямбда-типа. Опять же, вы ожидаете, что результаты будут соответствовать предыдущему фрагменту; и это справедливо для случая, когда объявления using закомментированы, или если оба без рапорта. Но если вы только прокомментируете одно, а не другое, вещи внезапно отличаются друг от друга: теперь только MSVC жалуется, как я ожидал, в то время как Clang и gcc оба считают, что это нормально - и использовать оба унаследованных элемента для разрешения перегрузки, несмотря на то, что только один из них привнесен в using!
Вопрос №2: кто прав в этом случае? Опять же, я ожидаю, что это будет MSVC, но почему же Clang и gcc не согласны? И что еще более важно, почему это отличается от предыдущего фрагмента? Я бы ожидал, что лямбда-тип будет вести себя точно так же, как с заданным вручную типом с перегруженным operator()...