Development Tip

C ++ CRTP (정적 다형성) 및 파생 클래스의 typedef 사용

yourdevel 2020. 12. 12. 12:35
반응형

C ++ CRTP (정적 다형성) 및 파생 클래스의 typedef 사용


나는 읽기 위키 백과 문서 다형성 : 정적 (컴파일시 읽기)을 수행하는 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;
}

BTW, 추가 템플릿 매개 변수를 사용하는 해결 방법이 있지만 마음에 들지 않습니다. 상속 체인에 많은 유형을 전달할 때 매우 장황해질 것입니다.

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

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

편집하다:

이 상황에서 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>'


derivedbase기본 클래스 목록에서 템플릿 인수로 사용할 때 불완전 합니다.

일반적인 해결 방법은 특성 클래스 템플릿을 사용하는 것입니다. 여기에 귀하의 예가 있습니다. 이것은 트레이 트를 통해 파생 클래스의 형식과 함수를 모두 사용할 수있는 방법을 보여줍니다.

// 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사용하는 모든 유형을 base전문화하고 각 전문화가 필요한 모든 멤버를 제공하는지 확인 하기 만하면 base됩니다.


트레이 트 사용의 한 가지 작은 단점은 각 파생 클래스에 대해 하나씩 선언해야한다는 것입니다. 다음과 같이 덜 장황하고 중복되는 해결 방법을 작성할 수 있습니다.

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;
}

C ++ 14 typedef에서는 함수 auto반환 유형 추론을 제거 하고 사용할 수 있습니다 .

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

This works because the deduction of the return type of base::foo is delayed until derived_t is complete.


An alternative to type traits that requires less boilerplate is to nest your derived class inside a wrapper class that holds your typedefs (or using's) and pass the wrapper as a template argument to your base class.

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);
}

I know that this is basically the workaround you found and don't like, but I wanted to document it and also to say that it is basically the current solution to this problem.

I have been looking for a way to do this for a while and never found a good solution. The fact that it is not possible is the reason why ultimately, things like boost::iterator_facade<Self, different_type, value_type, ...> need many parameters.

Of course we would like something something like this to work:

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;}

If this was possible, all the traits of the derived class could be passed implicitly ot the base class. The idiom I found to get the same effect is to pass the traits to the base class entirely.

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

The drawback is that the trait in the derived class has to be accessed with a qualified typename or reenabled by using.

참고URL : https://stackoverflow.com/questions/6006614/c-static-polymorphism-crtp-and-using-typedefs-from-derived-classes

반응형