IT story

템플릿 템플릿 매개 변수의 용도는 무엇입니까?

hot-time 2020. 4. 25. 09:57
반응형

템플릿 템플릿 매개 변수의 용도는 무엇입니까?


정책 기반 클래스 디자인을 수행하기 위해 템플릿 템플릿 매개 변수 (템플릿을 매개 변수로 사용하는 템플릿)를 사용하는 C ++의 몇 가지 예를 보았습니다. 이 기술에는 어떤 다른 용도가 있습니까?


템플릿 템플릿 구문을 사용하여 유형이 다음과 같은 다른 템플릿에 의존하는 템플릿 인 매개 변수를 전달해야한다고 생각합니다.

template <template<class> class H, class S>
void f(const H<S> &value) {
}

여기는 H템플릿이지만이 함수가의 모든 전문 분야를 처리하기를 원했습니다 H.

참고 : 나는 수년간 c ++을 프로그래밍 해 왔으며 한 번만 필요했습니다. 나는 그것이 거의 필요하지 않은 기능이라는 것을 알았습니다 (물론 필요할 때 편리합니다!).

나는 좋은 예를 생각하고 정직하기 위해 노력했지만 대부분의 경우 이것이 필요하지 않지만 예를 생각해 봅시다. 그건 척하자 std::vector 하지 않는 있습니다 typedef value_type.

그렇다면 벡터 요소에 적합한 유형의 변수를 만들 수있는 함수를 어떻게 작성 하시겠습니까? 이 작동합니다.

template <template<class, class> class V, class T, class A>
void f(V<T, A> &v) {
    // This can be "typename V<T, A>::value_type",
    // but we are pretending we don't have it

    T temp = v.back();
    v.pop_back();
    // Do some work on temp

    std::cout << temp << std::endl;
}

참고 : std::vector유형과 할당 자라는 두 가지 템플릿 매개 변수 있으므로 두 매개 변수를 모두 수락해야합니다. 운좋게도 타입 공제 때문에 정확한 타입을 명시 적으로 작성할 필요는 없습니다.

다음과 같이 사용할 수 있습니다.

f<std::vector, int>(v); // v is of type std::vector<int> using any allocator

또는 더 나은 방법으로 다음을 사용할 수 있습니다.

f(v); // everything is deduced, f can deal with a vector of any type!

업데이트 :이 고안 된 예조차도 설명 적이기는하지만 c ++ 11 도입으로 인해 더 이상 놀라운 예가 아닙니다 auto. 이제 동일한 함수를 다음과 같이 작성할 수 있습니다.

template <class Cont>
void f(Cont &v) {

    auto temp = v.back();
    v.pop_back();
    // Do some work on temp

    std::cout << temp << std::endl;
}

이것이 이런 유형의 코드를 작성하는 것을 선호하는 방법입니다.


실제로 템플릿 템플릿 매개 변수의 사용 사례는 다소 분명합니다. C ++ stdlib에 표준 컨테이너 유형에 대한 스트림 출력 연산자를 정의하지 못하는 격차가 있음을 알게되면 다음과 같이 작성하십시오.

template<typename T>
static inline std::ostream& operator<<(std::ostream& out, std::list<T> const& v)
{
    out << '[';
    if (!v.empty()) {
        for (typename std::list<T>::const_iterator i = v.begin(); ;) {
            out << *i;
            if (++i == v.end())
                break;
            out << ", ";
        }
    }
    out << ']';
    return out;
}

그런 다음 vector에 대한 코드가 동일하다는 것을 알 수 있습니다. forward_list는 동일합니다. 실제로는 많은지도 유형에 대해서도 여전히 동일합니다. 이러한 템플릿 클래스는 메타 인터페이스 / 프로토콜을 제외하고는 공통점이 없으며 템플릿 템플릿 매개 변수를 사용하면 모든 공통성을 캡처 할 수 있습니다. 템플릿을 작성하기 전에 시퀀스 컨테이너가 값 유형과 할당 자에 대해 두 개의 템플릿 인수를 허용한다는 것을 상기하기 위해 참조를 확인하는 것이 좋습니다. 할당자가 기본값으로 설정되어 있지만 템플릿 연산자 << :에 여전히 존재해야합니다.

template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...

Voila는 표준 프로토콜을 준수하는 모든 현재 및 미래의 시퀀스 컨테이너에 대해 자동으로 작동합니다. 믹스에 맵을 추가하려면 4 개의 템플릿 매개 변수를 허용한다는 점을 참고하여 살짝 살펴 봐야하므로 위의 4-arg 템플릿 템플릿 매개 변수가있는 연산자 <<의 다른 버전이 필요합니다. 또한 std : pair가 이전에 정의한 시퀀스 유형에 대해 2-arg operator <<로 렌더링하려고 시도하므로 std :: pair에 대한 전문화 만 제공합니다.

Btw, variadic 템플릿을 허용하고 따라서 variadic 템플릿 템플릿 인수를 허용하는 C + 11을 사용하면 단일 연산자 <<를 사용하여 모든 것을 지배 할 수 있습니다. 예를 들면 다음과 같습니다.

#include <iostream>
#include <vector>
#include <deque>
#include <list>

template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
{
    os << __PRETTY_FUNCTION__ << '\n';
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

int main()
{
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';

    return 0;
}

산출

std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = float, C = vector, Args = <std::__1::allocator<float>>]
1.1 2.2 3.3 4.4 
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = char, C = list, Args = <std::__1::allocator<char>>]
a b c d 
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>]
1 2 3 4 

다음은 Andrei Alexandrescu의 'Modern C ++ Design-Generic Programming and Design Patterns Applied' 에서 가져온 간단한 예입니다 .

정책 패턴을 구현하기 위해 템플릿 템플릿 매개 변수가있는 클래스를 사용합니다.

// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
   ...
};

그는 설명 : 정책 클래스의 템플릿 인수를 일반적으로 호스트 클래스는 이미 알고있는, 또는 쉽게 추론 할 수 있습니다. 위의 예에서 WidgetManager는 항상 Widget 유형의 객체를 관리하므로 사용자가 CreationPolicy를 인스턴스화 할 때 위젯을 다시 지정해야하는 것은 중복되고 잠재적으로 위험합니다.이 경우 라이브러리 코드는 정책을 지정하기 위해 템플릿 템플릿 매개 변수를 사용할 수 있습니다.

결과적으로 클라이언트 코드는보다 우아한 방식으로 'WidgetManager'를 사용할 수 있습니다.

typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;

템플릿 템플릿 인수가없는 정의에는 더 번거롭고 오류가 발생하기 쉬운 방법이 필요했습니다.

typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;

CUDA Convolutional 신경망 라이브러리 의 또 다른 실용적인 예가 있습니다 . 다음과 같은 수업 템플릿이 있습니다.

template <class T> class Tensor

실제로는 n 차원 행렬 조작을 구현합니다. 자식 클래스 템플릿도 있습니다 :

template <class T> class TensorGPU : public Tensor<T>

GPU와 동일한 기능을 구현합니다. 두 템플릿 모두 float, double, int 등과 같은 모든 기본 유형에서 작동 할 수 있으며 클래스 템플릿도 있습니다 (단순화).

template <template <class> class TT, class T> class CLayerT: public Layer<TT<T> >
{
    TT<T> weights;
    TT<T> inputs;
    TT<int> connection_matrix;
}

템플릿 템플릿 구문을 사용하는 이유는 클래스의 구현을 선언 할 수 있기 때문입니다.

class CLayerCuda: public CLayerT<TensorGPU, float>

float 및 GPU 유형의 가중치와 입력이 모두 있지만 connection_matrix는 CPU (TT = Tensor를 지정하여) 또는 GPU (TT = TensorGPU를 지정하여)에서 항상 int입니다.


하위 템플릿 집합에 대한 "인터페이스"를 제공하기 위해 CRTP를 사용한다고 가정합니다. 부모와 자식 모두 다른 템플릿 인수에서 매개 변수입니다.

template <typename DERIVED, typename VALUE> class interface {
    void do_something(VALUE v) {
        static_cast<DERIVED*>(this)->do_something(v);
    }
};

template <typename VALUE> class derived : public interface<derived, VALUE> {
    void do_something(VALUE v) { ... }
};

typedef interface<derived<int>, int> derived_t;

'int'의 복제에 유의하십시오. 이는 실제로 두 템플리트에 지정된 동일한 유형 매개 변수입니다. 이 중복을 피하기 위해 DERIVED 용 템플릿 템플릿을 사용할 수 있습니다.

template <template <typename> class DERIVED, typename VALUE> class interface {
    void do_something(VALUE v) {
        static_cast<DERIVED<VALUE>*>(this)->do_something(v);
    }
};

template <typename VALUE> class derived : public interface<derived, VALUE> {
    void do_something(VALUE v) { ... }
};

typedef interface<derived, int> derived_t;

파생 된 템플릿에 다른 템플릿 매개 변수를 직접 제공하지 않아도됩니다 . "인터페이스"는 여전히 그것들을받습니다.

또한 파생 된 템플릿에서 액세스 할 수있는 유형 매개 변수에 따라 "인터페이스"에 typedef를 작성할 수 있습니다.

지정되지 않은 템플릿에 typedef를 입력 할 수 없으므로 위의 typedef가 작동하지 않습니다. 그러나 이것은 작동합니다 (그리고 C ++ 11은 템플릿 typedef를 기본적으로 지원합니다).

template <typename VALUE>
struct derived_interface_type {
    typedef typename interface<derived, VALUE> type;
};

typedef typename derived_interface_type<int>::type derived_t;

불행히도 파생 된 템플릿의 각 인스턴스화에 대해 하나의 파생 된 _ 인터페이스 _ 유형이 필요합니다.


이것은 내가 만난 것입니다.

template<class A>
class B
{
  A& a;
};

template<class B>
class A
{
  B b;
};

class AInstance : A<B<A<B<A<B<A<B<... (oh oh)>>>>>>>>
{

};

다음과 같이 해결할 수 있습니다.

template<class A>
class B
{
  A& a;
};

template< template<class> class B>
class A
{
  B<A> b;
};

class AInstance : A<B> //happy
{

};

또는 (작업 코드) :

template<class A>
class B
{
public:
    A* a;
    int GetInt() { return a->dummy; }
};

template< template<class> class B>
class A
{
public:
    A() : dummy(3) { b.a = this; }
    B<A> b;
    int dummy;
};

class AInstance : public A<B> //happy
{
public:
    void Print() { std::cout << b.GetInt(); }
};

int main()
{
    std::cout << "hello";
    AInstance test;
    test.Print();
}

pfalcon에서 제공하는 variadic 템플릿을 사용하는 솔루션에서 variadic 전문화의 탐욕스러운 특성으로 인해 std :: map에 대한 ostream 연산자를 실제로 전문화하는 것이 어렵다는 것을 알았습니다. 다음은 나를 위해 약간 수정되었습니다.

#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <map>

namespace containerdisplay
{
  template<typename T, template<class,class...> class C, class... Args>
  std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
  {
    std::cout << __PRETTY_FUNCTION__ << '\n';
    for (auto const& obj : objs)
      os << obj << ' ';
    return os;
  }  
}

template< typename K, typename V>
std::ostream& operator << ( std::ostream& os, 
                const std::map< K, V > & objs )
{  

  std::cout << __PRETTY_FUNCTION__ << '\n';
  for( auto& obj : objs )
  {    
    os << obj.first << ": " << obj.second << std::endl;
  }

  return os;
}


int main()
{

  {
    using namespace containerdisplay;
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';
  }

  std::map< std::string, std::string > m1 
  {
      { "foo", "bar" },
      { "baz", "boo" }
  };

  std::cout << m1 << std::endl;

    return 0;
}

여기 내가 방금 사용한 것에서 일반화 된 것이 있습니다. 매우 간단한 예 이므로 게시하고 있으며 기본 인수와 함께 실제 사용 사례를 보여줍니다.

#include <vector>

template <class T> class Alloc final { /*...*/ };

template <template <class T> class allocator=Alloc> class MyClass final {
  public:
    std::vector<short,allocator<short>> field0;
    std::vector<float,allocator<float>> field1;
};

코드의 가독성을 높이고 추가적인 유형 안전성을 제공하며 컴파일러 노력을 절약합니다.

컨테이너의 각 요소를 인쇄하고 싶다면 템플릿 템플릿 매개 변수없이 다음 코드를 사용할 수 있습니다

template <typename T> void print_container(const T& c)
{
    for (const auto& v : c)
    {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

또는 템플릿 템플릿 매개 변수 사용

template< template<typename, typename> class ContainerType, typename ValueType, typename AllocType>
void print_container(const ContainerType<ValueType, AllocType>& c)
{
    for (const auto& v : c)
    {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

정수 say를 전달한다고 가정하십시오 print_container(3). 전자의 경우 템플릿은 cfor 루프에서의 사용법에 대해 불평하는 컴파일러에 의해 인스턴스화되며 , 후자는 일치하는 유형을 찾을 수 없으므로 템플릿을 전혀 인스턴스화하지 않습니다.

일반적으로 템플릿 클래스 / 함수가 템플릿 클래스를 템플릿 매개 변수로 처리하도록 설계된 경우 명확하게하는 것이 좋습니다.


버전이 지정된 유형에 사용합니다.

와 같은 템플릿을 통해 버전이 지정된 유형 인 MyType<version>경우 버전 번호를 캡처 할 수있는 함수를 작성할 수 있습니다.

template<template<uint8_t> T, uint8_t Version>
Foo(const T<Version>& obj)
{
    assert(Version > 2 && "Versions older than 2 are no longer handled");
    ...
    switch (Version)
    {
    ...
    }
}

따라서 각 유형에 과부하가 걸리지 않고 전달되는 유형의 버전에 따라 다른 작업을 수행 할 수 있습니다. 또한 일반적인 방식으로 가져 MyType<Version>오고 리턴 하는 변환 함수를 가질 수 있으며 , 이전 버전에서 최신 버전의 유형을 리턴 MyType<Version+1>하는 ToNewest()함수 를 갖도록 재귀 할 수도 있습니다 (오래 동안 저장되었을 수있는 로그에 매우 유용함). 하지만 최신 도구로 처리해야합니다).

참고 URL : https://stackoverflow.com/questions/213761/what-are-some-uses-of-template-template-parameters

반응형