Неверный ковариантный тип с клонируемым классом CRTP

Я пытаюсь реализовать класс Clonable с помощью CRTP. Однако мне нужен абстрактный класс с чистым виртуальным методом клонирования, переопределенным дочерними классами. Чтобы это произошло, мне нужно, чтобы функция клонирования возвращала ковариантный тип возвращаемого значения. Я сделал этот код ниже, и компилятор кричит мне об этой ошибке:

main.cpp:12:5: error: return type of virtual function 'clone' is not covariant with the return type of the function it overrides ('B *' is not derived from 'AbstractClonable *')

Класс «B» кажется дочерним классом AbstractClonable, и даже в двух направлениях! Как я могу это решить? Большое Вам спасибо. Я пробовал как с clang 3.6, так и с GCC 4.9.2

struct AbstractClonable {
    virtual AbstractClonable* clone() const = 0;
};

template<typename T>
struct Clonable : virtual AbstractClonable {
    T* clone() const override {
        return new T{*dynamic_cast<const T*>(this)};
    }
};

struct A : virtual AbstractClonable {

};

struct B : A, Clonable<B> {

};

person Guillaume Racicot    schedule 15.05.2015    source источник
comment
Вам действительно нужен AbstractClonable? Почему? Мне действительно любопытно. Есть ли законные варианты использования? Вы можете его клонировать, и что бы вы сделали с результатом? Клонировать его снова?   -  person n. 1.8e9-where's-my-share m.    schedule 15.05.2015
comment
да. Если у меня есть коллекция A и A абстрактная, и мне нужно клонировать каждый объект, мне нужно что-то, что говорит о том, что мне нужно реализовать клонирование в подклассах. Поскольку A является абстрактным, он не может наследовать от Clonable из-за нового в функции клонирования. Я придумал AbstractClonable.   -  person Guillaume Racicot    schedule 15.05.2015


Ответы (3)


Даже если B действительно является производным от Clonable<B>, проблема здесь в том, что конструкция Clonable<B> недействительна, поскольку она определяет

B* clone() const override

что, конечно, не является переопределением AbstractClonable::clone(), поскольку компилятор не видит B в этот момент как дочерний элемент AbstractClonable. Поэтому я считаю, что проблема заключается в том, что компилятор не может построить Clonable<B> базу B.

Обходной путь (но не совсем такой, как то, что вы хотите) - определить

Clonable* clone() const override

в Clonable. Как вы упомянули в комментарии, вы также можете определить бесплатную функцию

template<typename T> 
T* clone(const T* object) 
{ 
    return static_cast<T*>(object->clone()); 
}

По теме: Получены любопытно повторяющиеся шаблоны и ковариация

person vsoftco    schedule 15.05.2015
comment
Разве виртуальное наследование не должно об этом позаботиться? - person W.F.; 15.05.2015
comment
AFAIK, виртуальная деривация просто удаляет повторяющуюся базу с изображения. Я не думаю, что это заставляет работать эту конструкцию. - person vsoftco; 15.05.2015
comment
B является дочерним элементом A, а A является дочерним элементом AbstractClonable. И мы видим, что B является потомком класса Clonable ‹B›, который является потомком AbstractClonable. B дважды связан с AbstractClonable. Как это B не является потомком AbstractClonable? - person Guillaume Racicot; 15.05.2015
comment
B является дочерним по отношению к AbstractClonable, но при первом построении Clonable<B> B является неполным типом, а Clonable<B> не рассматривается как дочерний элемент AbstractClonable. Я не думаю, что компилятор может успешно полностью определить B, не пытаясь сначала разрешить его базовые классы. Должен сказать, что я не уверен в этом на 100%. Связанный (но без принятого ответа) stackoverflow.com/q/17201268/3093378 - person vsoftco; 15.05.2015
comment
Это определенно не та семантика, которую я искал, но я думаю, это поможет. - person Guillaume Racicot; 15.05.2015
comment
Это помогло с вашим обходным путем и следующей функцией: template<typename T> T* clone(const T* object) { return static_cast<T*>(object->clone()); } Можете ли вы добавить это к своему ответу? Я отмечу это как принятый ответ. - person Guillaume Racicot; 15.05.2015
comment
@GuillaumeRacicot Вы имели в виду обновленную правку? Меня устраивает. - person vsoftco; 15.05.2015
comment
Я добавил новый клон функции вне области действия класса, чтобы автоматически отбрасывать объект. Пользуюсь так: clone(myB) - person Guillaume Racicot; 15.05.2015

Да, B является производным от AbstractClonable, но компилятор не знает этого во время создания экземпляра Clonable<B>, потому что B в этот момент еще не завершен.

C++14 §10.3/8:

Если тип класса в ковариантном типе возврата D::f отличается от типа B::f, тип класса в типе возвращаемого значения D::f должен быть полным в момент объявления D::f или должен быть типом класса D .

У класса есть специальное разрешение на использование себя в ковариантном возвращаемом типе. Другим классам, включая базы CRTP, необходимо дождаться завершения класса, прежде чем объявлять ковариантную функцию.

Решить проблему можно с помощью идиомы невиртуального интерфейса (NVI):

class AbstractClonable {
protected:
    virtual AbstractClonable* do_clone() const = 0;
public:
    AbstractClonable *clone() const {
        return do_clone();
    }
};

template<typename T>
class Clonable : public virtual AbstractClonable {
    Clonable* do_clone() const override { // Avoid using T in this declaration.
        return new T{*dynamic_cast<const T*>(this)};
    }
public:
    T *clone() const { // But here, it's OK.
        return static_cast< T * >( do_clone() );
    }
};
person Potatoswatter    schedule 15.05.2015

Я думаю проблема в том, что

T* clone() const override{
    return new T{*dynamic_cast<const T*>(this)};
}

возвращает B * вместо AbstractClonable *.

person trbvm    schedule 15.05.2015
comment
Да, он возвращает B *, и это моя цель! Разве они не должны быть действительными ковариантными типами, поскольку B наследуется от AbstractClonable? - person Guillaume Racicot; 15.05.2015
comment
Поскольку существует множественное виртуальное наследование, приведение от B * к AbstractClonable * требует выдачи дополнительного кода. Этот код будет выполнять поиск базового класса, используя указатель на виртуальный базовый класс. Если вы вызываете метод clone (), используя указатель на AbstractClonable, компилятор, вероятно, не сможет правильно выполнить приведение типов. Поэтому я не думаю, что B * и AbstractClonable * можно считать ковариантными. - person trbvm; 15.05.2015