Почему выводит A A, а не A B?
struct A {
virtual void foo(char x = 'A'){
std::cout << x << ' ';
};
};
struct B : A {
void foo(char x = 'B') override {
A::foo(x);
}
};
int main() {
A* a = new A{};
A* b = new B{};
a->foo();
b->foo();
}
Потому что значения аргументов по умолчанию обрабатываются при компиляции, а виртуальная диспетчеризация - во время выполнения.
Что видит компилятор? вызов foo() для типа A - и понимает, что нужно подставить значение аргумента по умолчанию (для типа A это A).
При вызове вызывается B::foo() с аргументом, полученным при компиляции - а именно, A.
Вот представьте, что в каком-то файле будет какой-то опосредованный потомок с еще каким-то аргументом по умолчанию - как вообще компилятор сможет его найти? Это что, компилятор в такой ситуации должен хранить кроме таблицы виртуальных функций еще и таблицу значений аргументов по умолчанию (причем для тех потомков, которых во время компиляции еще и в проекте нет)?
Всё просто на самом деле.
В этом месте b->foo(); компилятор должен проверить и построить легальный код для вызова. Он прекрасно видит, что есть один вариант foo, который можно использовать таким образом (вызов без указания параметра), и ему нужно здесь передать в функцию аргумент: b->foo(параметр-заданный-по-умолчанию);. Внимание, у нас этап компиляции, и компилятору неизвестно объект какого типа реально окажется в памяти. Поэтому сначала кратенько напомню, что в C++ имеются статический и динамический типы.
Динамический тип - это тип того объекта, который непосредственно будет создан и лежать в памяти, т.е. в нашем случае это объект типа B.
Статический тип - это тип выражения, который определяется в результате анализа программы без учета выполнения семантики. В нашем случае в выражении b->foo(); b - это указатель на объект типа A.
То, что в памяти при b->foo(); окажется объект типа B будет известно только во время выполнения, в общем случае, во время компиляции компилятору неизвестно какой реально тип имеет объект, на который указывает b, но код вызова b->foo(); он построить должен уже сейчас, так что у него нет другого выбора, кроме как взять параметр по-умолчанию, исходя из статического типа, и поставить его в аргументы, т.е. параметр будет взят из A::foo, и будет построен код аналогичный b->foo('A');.
Именно об этом говорит пункт 11.3.6/10 (N4659) стандарта C++:
11.3.6 Default arguments
Сборка персонального компьютера от Artline: умный выбор для современных пользователей