The return type of an overriding function shall be either identical to the return type of the overridden function or covariant with the classes of the functions.
If a function D::f overrides a function B::f, the return types of the functions are covariant if they satisfy the following criteria:
- both are pointers to classes, both are lvalue references to classes, or both are rvalue references to classes102
- the class in the return type of B::f is the same class as the class in the return type of D::f, or is an unambiguous and accessible direct or indirect base class of the class in the return type of D::f
- both pointers or references have the same cv-qualification and the class type in the return type of D::f has the same cv-qualification as or less cv-qualification than the class type in the return type of B::f.
Почему сделано так а не иначе, объяснения, данные здесь
3.56 signature
⟨class member function⟩ name, parameter-type-list, class of which the function is a member, cv-qualifiers (if any), ref-qualifier (if any), and trailing requires-clause (if any)
watchmaker, не прокомментируешь, с чем не согласен?
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, Muxa, Вы писали:
M>Почему компилятор такое позволяет для F0 и F1, но не позволяет для F2 и F3?
Ковариантность по возвращаемому типу — логична и не противоречит принципу подстановки.
Type Base::fun();
SubType Derived::fun();
Type t = ((Base&)derived).fun(); // да, t фактически SubType
Также принципу подстановки не противоречит и контравариантность по входящему типу
void Base::fun(SubType);
void Derived::fun(Type t);
SubType s;
((Base&)derived).fun(s); // t фактически SubType
Но С++ не настолько силён во всех этих делах, поэтому ограничивается только лишь ковариантностью, только лишь возвращаемого типа, и только лишь для указателей и ссылок.
То есть, даже константность навесить нельзя, не говоря уже про целочисленные типы разной разрядности или всякие там шаблоны, где можно было бы реализовать любую логику.
Почему для указателей и ссылок, — понятно. Потому что у них всех одинаковое бинарное представление и элементарный static_cast, что позволяет вызывать виртуальные функции и сразу знать, что делать с результатом.
(Хотя, если SubType — это указатель на структуру со смещением базы, или с виртуальным наследованием, там всё равно компилятору приходится делать функцию-прокладку, которая этот static_cast выполнит).
Почему только для возвращаемого типа, — чтобы себе не забивать голову.
Например, у нас есть такая весёлая штука, как перегрузка.
где T1 и T2 — супертипы S. Неважно, равноправные или один тоже субтип другого.
Вопрос: которая из этих функций переопределяет функцию базы?
А если у базы тоже перегруженные функции, какая функция наследника переопределит какую функцию базы?
Вот поэтому, — из-за того, что выбор перегрузки определяется типами аргументов, — и принято волюнтаристское решение, что по аргументам функции инвариантны, а не контравариантны.
Ах да, кстати. Контравариантность в С++ появляется в работе с указателями на члены. И всё.
void Base::foo();
void Derived::bar();
// Derived::foo тоже есть, - унаследовано или переопределено, неважноvoid(Base::*base_member)() = &Base::foo;
void(Derived::*derived_member)() = base_member; // ok
(derived.*base_member)(); // ok, у наследника валидные члены базы
(derived.*derived_member)(); // ok
(base.*base_member)(); // ok
(base.*derived_member)(); // не ок, потому что а вдруг там Derived::bar, который не умеет работать с this типа Base* ?
Здравствуйте, Кодт, Вы писали:
К>Ах да, кстати. Контравариантность в С++ появляется в работе с указателями на члены. И всё.
std::function еще ковариантен по возвращаемым значениям и контрвариантем по аргументам.
#include <functional>
struct foo {};
struct bar : foo {};
bar* test(const foo&)
{
return nullptr;
}
int main() {
std::function<const foo*(bar&)> f = test;
bar b;
f(b);
}
Здравствуйте, Voivoid, Вы писали:
V>Здравствуйте, Кодт, Вы писали:
К>>Ах да, кстати. Контравариантность в С++ появляется в работе с указателями на члены. И всё. V>std::function еще ковариантен по возвращаемым значениям и контрвариантем по аргументам.
Причём он не по указателям-ссылкам, а вообще по static_cast.
#pragma GCC diagnostic error "-Wconversion"#include <functional>
struct A {};
struct B : A {};
struct C {
operator A()const { return {}; }
};
B* fff(const A*) { return nullptr; }
B ggg(A a) { return {}; }
C hhh(A a) { return {}; };
int ttt(long) { return 0; }
int main() {
std::function<const A*(B*)> f = fff;
std::function<A(B)> g = ggg;
std::function<A(C)> h = hhh;
std::function<long(int)> t = ttt;
B b;
C c;
int d = 0;
f(&b);
g(b);
h(c);
t(d);
}