C ++ статический полиморфизм (CRTP) и использование typedef из производных классов - PullRequest
56 голосов
/ 15 мая 2011

Я прочитал статью Википедии о странно повторяющемся шаблонном шаблоне в C ++ для выполнения статического (читай: время компиляции) полиморфизма. Я хотел обобщить это так, чтобы я мог изменить типы возвращаемых функций на основе производного типа. (Кажется, это должно быть возможно, так как базовый тип знает производный тип из параметра шаблона). К сожалению, следующий код не будет компилироваться с использованием MSVC 2010 (сейчас у меня нет легкого доступа к gcc, поэтому я еще не пробовал). Кто-нибудь знает почему?

template <typename derived_t>
class base {
public:
    typedef typename derived_t::value_type value_type;
    value_type foo() {
        return static_cast<derived_t*>(this)->foo();
    }
};

template <typename T>
class derived : public base<derived<T> > {
public:
    typedef T value_type;
    value_type foo() {
        return T(); //return some T object (assumes T is default constructable)
    }
};

int main() {
    derived<int> a;
}

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

template <typename derived_t, typename value_type>
class base { ... };

template <typename T>
class derived : public base<derived<T>,T> { ... };

EDIT:

Сообщение об ошибке, которое MSVC 2010 выдает в этой ситуации, error C2039: 'value_type' : is not a member of 'derived<T>'

g ++ 4.1.2 (через codepad.org ) говорит error: no type named 'value_type' in 'class derived<int>'

Ответы [ 5 ]

58 голосов
/ 15 мая 2011

derived является неполным, когда вы используете его в качестве аргумента шаблона для base в своем списке базовых классов.

Обычный обходной путь - использование шаблона класса признаков.Вот твой пример, черт возьми.Это показывает, как вы можете использовать оба типа и функции из производного класса через черты.

// Declare a base_traits traits class template:
template <typename derived_t> 
struct base_traits;

// Define the base class that uses the traits:
template <typename derived_t> 
struct base { 
    typedef typename base_traits<derived_t>::value_type value_type;
    value_type base_foo() {
        return base_traits<derived_t>::call_foo(static_cast<derived_t*>(this));
    }
};

// Define the derived class; it can use the traits too:
template <typename T>
struct derived : base<derived<T> > { 
    typedef typename base_traits<derived>::value_type value_type;

    value_type derived_foo() { 
        return value_type(); 
    }
};

// Declare and define a base_traits specialization for derived:
template <typename T> 
struct base_traits<derived<T> > {
    typedef T value_type;

    static value_type call_foo(derived<T>* x) { 
        return x->derived_foo(); 
    }
};

Вам просто нужно специализировать base_traits для любых типов, которые вы используете для аргумента шаблона derived_t of base и убедитесь, что каждая специализация содержит всех членов, которые необходимы для base.

8 голосов
/ 09 января 2015

Один небольшой недостаток использования черт заключается в том, что вы должны объявлять его для каждого производного класса. Вы можете написать менее многословный и избыточный обходной путь, как это:

template <template <typename> class Derived, typename T>
class base {
public:
    typedef T value_type;
    value_type foo() {
        return static_cast<Derived<T>*>(this)->foo();
    }
};

template <typename T>
class Derived : public base<Derived, T> {
public:
    typedef T value_type;
    value_type foo() {
        return T(); //return some T object (assumes T is default constructable)
    }
};

int main() {
    Derived<int> a;
}
6 голосов
/ 12 августа 2016

В C ++ 14 вы можете удалить typedef и использовать функцию auto удержание типа возврата:

template <typename derived_t>
class base {
public:
    auto foo() {
        return static_cast<derived_t*>(this)->foo();
    }
};

Это работает, потому что удержание типа возврата base::foo задерживается доderived_t завершено.

2 голосов
/ 13 ноября 2017

Альтернативой чертам типов, требующим меньшего количества шаблонов, является вложение вашего производного класса в класс-оболочку, который содержит ваши определения типов (или использование), и передачу оболочки в качестве аргумента шаблона вашему базовому классу.

template <typename Outer>
struct base {
    using derived = typename Outer::derived;
    using value_type = typename Outer::value_type;
    value_type base_func(int x) {
        return static_cast<derived *>(this)->derived_func(x); 
    }
};

// outer holds our typedefs, derived does the rest
template <typename T>
struct outer {
    using value_type = T;
    struct derived : public base<outer> { // outer is now complete
        value_type derived_func(int x) { return 5 * x; }
    };
};

// If you want you can give it a better name
template <typename T>
using NicerName = typename outer<T>::derived;

int main() {
    NicerName<long long> obj;
    return obj.base_func(5);
}
0 голосов
/ 16 января 2019

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

Я долго искал способ сделать это и не нашел хорошего решения. Тот факт, что это невозможно, является причиной того, что в конечном итоге такие вещи, как boost::iterator_facade<Self, different_type, value_type, ...>, нуждаются во многих параметрах.

Конечно, мы бы хотели, чтобы что-то вроде этого работало:

template<class CRTP> 
struct incrementable{
    void operator++(){static_cast<CRTP&>(*this).increment();}
    using ptr_type = typename CRTP::value_type*; // doesn't work, A is incomplete
};

template<class T>
struct A : incrementable<A<T>>{
    void increment(){}
    using value_type = T;
    value_type f() const{return value_type{};}
};

int main(){A<double> a; ++a;}

Если бы это было возможно, все черты производного класса могли бы передаваться неявно от базового класса. Идиома, которую я нашел, чтобы получить тот же эффект, состоит в том, чтобы передать черты полностью базовому классу.

template<class CRTP, class ValueType> 
struct incrementable{
    void operator++(){static_cast<CRTP&>(*this).increment();}
    using value_type = ValueType;
    using ptr_type = value_type*;
};

template<class T>
struct A : incrementable<A<T>, T>{
    void increment(){}
    typename A::value_type f() const{return typename A::value_type{};}
//    using value_type = typename A::value_type;
//    value_type f() const{return value_type{};}
};

int main(){A<double> a; ++a;}

https://godbolt.org/z/2G4w7d

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

...