2017-08-21 13 views
8

Eksperymentowałem z Curiously Recurring Template Pattern dla generycznego pojedynczego argumentu funktora i mam dwie implementacje: jedną za pomocą parametru szablonu szablonu, który działa i drugą, w której próbuję uzyskać dostęp do pochodna Functor :: wpisz w klasie interfejsu. W tym ostatnim przykładzie, kompilator (gcc 5.4.0) informujeBrak typu o nazwie "typ" w klasie pochodnej CTRP

błąd: nie typ nazwany 'typ' w 'struct Cube < double>'

template<class T, template<class> class Functor> 
class FunctorInterface_1 { 
private: 
    const Functor<T> &f_cref; 
public: 
    FunctorInterface_1() : f_cref(static_cast<const Functor<T>&>(*this)) {} 
    T operator() (T val) const { return f_cref(val); } 
}; // FunctorInterface_1 (works) 

template<class Functor> 
class FunctorInterface_2 { 
private: 
    const Functor &f_cref; 
public: 
    using Ftype = typename Functor::type; 
    FunctorInterface_2() : f_cref(static_cast<const Functor&>(*this)) {} 
    Ftype operator() (Ftype val) const { return f_cref(val); } 
}; // FunctorInterface_2 (no type in Functor!) 

I następnie spróbuj skompilować z T = double w głównej() z następujących dwóch klas:

template<class T> 
struct Square : public FunctorInterface_1<T,Square> { 
    T operator()(T val) const { return val*val; } 
}; // Square 


template<class T> 
struct Cube : public FunctorInterface_2<Cube<T>> { 
    using type = T; 
    T operator() (T val) const { return val*val*val; } 
}; // Cube 

Czy można zmodyfikować przykład FunctorInterface_2/Cube do pracy, czy jest konieczne, aby klasa interfejsu była szablonem na T jako w pierwszym przykładzie? Dzięki!

EDIT: Korzystanie z gcc -std = C++ 14, można uzyskać drugi przykład, aby skompilować i uruchomić za pomocą automatycznego powrotu i typy argumentów w FunctorInterface_1 :: operator(), jednak, jak rozumiem, auto argumentem typy nie są częścią standardu C++ 14.

EDIT 2: Cóż, czuję się trochę gruby. Właśnie zdałem sobie sprawę, że mogę szablon FunctorInterface_1 :: operator() na nowy parametr, jednak dla aplikacji, którą mam na myśli, bardzo chciałbym mojej klasy bazowej, aby móc uzyskać dostęp do typów zdefiniowanych w klasie pochodnej.

Odpowiedz

4

Gdy linia

using Ftype = typename Functor::type; 

jest przetwarzany w klasie bazowej definicja Functor nie jest dostępna. Dlatego nie można używać Functor::type.

Jednym ze sposobów obejścia tego ograniczenia jest zdefiniowanie klasy cech.

// Declare a traits class. 
template <typename T> struct FunctorTraits; 

template<class Functor> 
class FunctorInterface_2 { 
    private: 
     const Functor &f_cref; 
    public: 

     // Use the traits class to define Ftype 
     using Ftype = typename FunctorTraits<Functor>::type; 

     FunctorInterface_2() : f_cref(static_cast<const Functor&>(*this)) {} 
     Ftype operator() (Ftype val) const { return f_cref(val); } 
}; // FunctorInterface_2 (no type in Functor!) 

// Forward declare Cube to specialize FunctorTraits 
template<class T> struct Cube; 

// Specialize FunctorTraits for Cube 
template <typename T> struct FunctorTraits<Cube<T>> 
{ 
    using type = T; 
}; 

template<class T> 
struct Cube : public FunctorInterface_2<Cube<T>> { 
    using type = T; 
    T operator() (T val) const { return val*val*val; } 
}; // Cube 

kod robocza: https://ideone.com/C1L4YW

+0

Jest to raczej wymyślna alternatywa dla dostarczania tego typu bezpośrednio jako parametr szablonu, tak jak w przypadku OP w jego pierwszej implementacji. I muszę wspomnieć, że jeśli typ jest przekazywany przez cechę, to wówczas klasa pochodna musi także wyodrębnić ją z cechy, czyli 'używając type = T;' powinno być 'using type = FunctorTraits > :: type;'. W przeciwnym razie taka implementacja jest naprawdę delikatna. – VTT

+0

Podejście Cech ma pewien wpływ na utrzymanie moich interfejsów w sposób agnostyczny, jednak wydaje się, że będzie to obciążać rozwój typów pochodnych. Aplikacja, którą mam na myśli, polega na złożonych operacjach na wektorach abstrakcyjnych, które będą miały jakiś typ OrdinalType do indeksowania i niektóre elementy typu dla danych zawartych w nim. Zajmuję się również badaniami nad czystymi cechami (a nie dziedziczeniem), ale badam zalety alternatyw dla wirtualnego dziedziczenia (z copius dynamic downcasting!), Które obecnie wdrożyliśmy. –

+0

@VTT, podejście cech ma inne zalety. Na przykład. klasa Functor nie musi być szablonem klasy, tak jak w pierwszym podejściu stosowanym przez OP. Zgadzam się z drugą kwestią. Jednak PO może nawet go nie potrzebować. –

2

Twój kod może być uproszczone do

template<typename TDerived> class 
Base 
{ 
    using Ftype = typename TDerived::type; 
}; 

template<typename T> class 
Derived: public Base<Derived<T>> 
{ 
    using type = T; 
}; 

Derived<int> wat; 

To nie działa, ponieważ w punkcie BaseDerived instancji klasy nie jest kompletna, a kompilator nie jest świadoma istnienia Derived::type jeszcze.

1

Musisz zrozumieć, że podczas wystąpienia Cube<T>FunctionInterface_2<Cube<T>> pobiera wystąpienia pierwszego. To oznacza, że ​​Cube<T> jest niekompletnym typem, podczas gdy tak się dzieje.
Kiedy kompilator dostaje się do linii przy użyciu Ftype = typename Functor::type;Functor jest niekompletny i nie można uzyskać dostępu do żadnego z zagnieżdżonych typów.

W twoim przypadku można zmienić FunctionInterface_2 do:

template<class Functor> 
class FunctorInterface_2 { 
private: 
    const Functor &f_cref; 
public: 
    FunctorInterface_2() : f_cref(static_cast<const Functor&>(*this)) {} 
    template <class TT> 
    auto operator() (TT && val) -> decltype(f_cref(val)) const { return f_cref(val); } 
}; 

Więc teraz dostęp do informacji o Functor jest opóźnione do czasu wywołania operator() z FunctionInterface_2 w którym momencie FunctionInterface_2 i Cube są w pełni instancja.

Powiązane problemy