Сначала рассмотрим этот код на С++:
#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()
...