& quot; Неверный ковариантный тип возврата & quot; ошибки во вложенных классах с методами, возвращающими основанные на шаблонах объекты

Следующий код C ++ дает мне эти ошибки при компиляции:

covariant.cpp:32:22: error: invalid covariant return type for ‘virtual Q<B> C::test()’
covariant.cpp:22:22: error:   overriding ‘virtual Q<A> B::test()’

Я не хочу менять линию virtual Q<B> test() {} в virtual Q<A> test() {} хотя это убирает ошибки компиляции. Есть ли другой способ решить эту проблему?

template <class T>
class Q
{
public:
Q() {}
virtual ~Q() {}
};

class A
{
public:
A() {}
virtual ~A() {}
};

class B
{
public:
B() {}
virtual ~B() {}

virtual Q<A> test() = 0;

};

class C : public B
{
public:
C() {}
virtual ~C() {}

virtual Q<B> test() {}
};

4

Решение

Q<B> а также Q<A> несвязанные классы. Представьте, что вы клиент B призвание test(): чему вы присваиваете результат, если не знаете, какой у него будет тип?

Тот факт, что оба Q<A> а также Q<B> Наличие экземпляров одного и того же шаблона класса не меняет того факта, что это два совершенно не связанных между собой класса, возможно, с совершенно разным макетом (из-за специализации шаблона).

Это ничем не отличается от выполнения:

struct X
{
virtual std::string test() = 0;
};

struct Y : X
{
virtual int test() { return 42; } // ERROR! std::string and int are
// unrelated, just as Q<A> and Q<B>
};

Клиент звонит test() на указатель на X будет ожидать, что результат будет string, но «Ой!», объект, на который указывает этот указатель, имеет тип Yи тип возвращаемого значения Y::test() является int, Что должно произойти? Авария во время выполнения?

Y y;
X* p = &y;
std::string s = p->test(); // D'OH!

C ++ является статически типизированным языком, что означает, что проверка типов выполняется во время компиляции. В этом случае сообщение от компилятора сообщит вам, что производный класс не соответствует интерфейсу класса, из которого он происходит.

Если вам интересно, что «неверный ковариантный тип возврата«означает, и, в частности, слово»ковариант«Это легко объяснить.

Предположим, у вас есть базовый класс B с виртуальной функцией foo() который возвращает X*:

struct B
{
virtual X* foo();
};

И предположим, что у вас есть класс D происходит от B что переопределяет foo() возвращая Y*, где Y это класс, полученный из X:

struct D : B
{
virtual Y* foo();
};

Это проблема? Ну, правильный ответ приходит от ответа на этот немного лучший вопрос: «Будет ли это проблемой для звонящего клиента? foo() что ожидает X* быть возвращенным?«

И ответ на этот вопрос, очевидно, «Нет», так как Y является производным классом Xтак что вы можете вернуть указатель на Y вместо указателя на X:

D d;
B* b = &d;
X* p = b->foo(); // Returns an Y*, but that's OK, because a pointer to Y can be
// assigned to a pointer to X

Это пример ковариантного возвращаемого типа. В вашем примере тип возвращаемого значения C::test() не является ковариантным относительно возвращаемого типа B::test(),

6

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

Функция с подписью B::test(void) возвращает объект типа Q<A>, в то время как C::test(void) (это та же самая подпись, поэтому вы перезаписываете функцию) возвращает Q<B>, Я думаю, что это невозможно.

Насколько я знаю, невозможно перегрузить функцию по типу возвращаемого значения, а при перезаписи родительских функций необходимо придерживаться того же возвращаемого типа.

Из стандарта §10.3 / 7

Тип возврата переопределяемой функции должен быть либо идентичным типу возврата переопределенной функции, либо ковариантным по отношению к классам функций. Если функция D :: f переопределяет функцию B :: f, возвращаемые типы функций ковариантны, если они удовлетворяют следующим критериям:

  • оба являются указателями на классы, оба являются lvalue-ссылками на классы, или оба являются rvalue-ссылками на классы
    classes112
  • класс в возвращаемом типе B :: f является тем же классом, что и класс в возвращаемом типе D :: f, или является однозначным и доступным прямым или косвенным базовым классом класса в возвращаемом типе D :: е
  • оба указателя или ссылки имеют одинаковую квалификацию cv, а тип класса в возвращаемом типе D :: f имеет ту же квалификацию cv, что и квалификационный класс или меньше, чем тип класса в возвращаемом типе B :: f.
1

Тебе этого не сделать. Переопределения виртуальных функций не могут изменить прототип функции, за исключением очень специфических случаев, таких как ковариантные возвращаемые типы.

Возврат Коварианта был бы действительным, если бы вы возвращали в виртуальном переопределении подкласс типа, возвращаемого в виртуальной базе. Но твой Q<A> а также Q<B> не связаны наследством. Дело в том, что B это подкласс A не имеет никакого значения здесь.

0