Статически выбранная функция и виртуальная функция

Недавно я видел этот стандартный абзац C ++ (http://eel.is/c++draft/expr.post#expr.call-5):

Если postfix-expression обозначает деструктор, тип выражения вызова функции void; в противном случае тип выражения вызова функции является типом возвращаемого значения статически выбранной функции (т. е. игнорируя ключевое слово virtual), даже если тип фактически вызванной функции отличается. Этот возвращаемый тип должен быть типом объекта, ссылочным типом или cv void.

Я не очень понимаю эту часть:

тип выражения вызова функции — это тип возврата статически выбранной функции (т.е. игнорирование виртуального ключевого слова), даже если тип фактически вызванной функции отличается.

  1. Что такое статически выбранная функция здесь?
  2. Как виртуальная функция может быть выбрана статически? Я всегда думал, что он выбран во время выполнения.
  3. даже если тип фактически вызванной функции отличается.

Как выражение вызова может на самом деле вызвать функцию другого типа, который был выбран?

2

Решение

Во-первых, пример для иллюстрации.

struct B {
virtual B* f() { return this; }
};

struct D : B {
D* f() override { return this; }
};

void bar(B*) {}
void bar(D*) {}

int main() {
D d;
B& b = d;
bar(b.f()); // calls `bar(B*)`
}

Здесь выражение postfix b.f обозначает функцию. Это B::fи его тип возврата B*, Хотя при переопределении f указанный тип возврата является ковариантным (D*). Тот факт, что реальный вызов (предположительно) разрешен во время выполнения, не меняет того факта, что мы выбираем функцию идентичность статически. Это уместно, когда есть перегрузка. Одно и то же имя функции может обозначать две или более функций, и это разрешение перегрузки, которое (статически) выбирает, какую перегрузку вызывать. Эта перегрузка может быть переопределена в производном классе, но опять-таки его идентичность является статической.

3

Другие решения

виртуальные функции могут иметь ковариантный тип возврата,

так с

struct Base
{
virtual ~Base() = default;
virtual Base* Clone() const { return new Base(*this); }
};

struct Derived : Base
{
// covariant return type:
Derived* Clone() const override { return new Derived(*this); }
};

затем

Derived d;

Base& b = d;

auto* clonePtr = b.Clone(); // `auto` is `Base`, even if `Derived::Clone` is called.
// runtime type of `clonePtr` is `Derived`
std::unique_ptr<Base> clone(clonePtr); // Done in 2 steps for explanation
7