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


238

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


4
나는 다른 방향 (FP, 하스켈 등)에서 온이에 착륙 : stackoverflow.com/questions/2565097/higher-kinded-types-with-c
— 에릭 Kaplun

답변:


197

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

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

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


1
f가 라이브러리의 사용자에 의해 정의 된 함수이면, 사용자는 std :: allocator <T>를 인수로 전달해야합니다. std :: allocator 인수가없는 버전은 std :: vector의 기본 매개 변수를 사용하여 작동했을 것으로 예상됩니다. 이 wrt C ++ 0x에 대한 업데이트가 있습니까?
— amit

할당자를 제공 할 필요는 없습니다. 중요한 것은 템플릿 템플릿 매개 변수가 올바른 개수의 인수에 대해 정의되었다는 것입니다. 그러나이 함수는 "유형"또는 의미가 무엇인지 신경 쓰지 않아야합니다. 다음은 C ++ 98에서 잘 작동합니다.template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
— pfalcon

인스턴스가 왜 궁금 f<vector,int>하지 f<vector<int>>.
— bobobobo

2
@bobobobo이 두 가지는 다른 의미입니다. f<vector,int>의미 f<ATemplate,AType>, f<vector<int>>의미f<AType>
— user362515

@phaedrus : (많은 나중에 ...) 좋은 점은 할당자를 일반화하고 예제를보다 명확하게하기 위해 예제를 개선했습니다.
— Evan Teran

163

실제로 템플릿 템플릿 매개 변수의 사용 사례는 다소 분명합니다. 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 

9
이것은 템플릿 템플릿 매개 변수의 좋은 예입니다. 모두가 처리해야 할 경우를 보여줍니다.
— Ravenwater

3
이것은 C ++ 템플릿에서 가장 눈에 띄는 답변입니다. @WhozCraig 템플릿 확장 정보는 어떻게 얻었습니까?
— Arun

3
@Arun gcc는이라는 매크로를 지원 __PRETTY_FUNCTION__하는데, 무엇보다도 템플릿 매개 변수 설명을 일반 텍스트로보고합니다. clang도 마찬가지입니다. (가시다시피) 가장 편리한 기능입니다.
— WhozCraig

20
여기서 템플릿 템플릿 매개 변수는 실제로 값을 추가하지 않습니다. 클래스 템플릿의 주어진 인스턴스로 일반 템플릿 매개 변수를 사용할 수도 있습니다.
— David Stone

9
데이비드 스톤에 동의해야합니다. 여기서 템플릿 템플릿 매개 변수를 가리킬 필요가 없습니다. 일반 템플릿 (템플릿 <typename Container>)을 만드는 것이 훨씬 간단하고 똑같이 효과적입니다. 나는이 게시물이 꽤 오래되었다는 것을 알고 있으므로 템플릿 템플릿에 대한 정보를 찾고이 답변을 우연히 발견하는 사람들을 위해 2 센트 만 추가하고 있습니다.
— Jim Vargo

67

다음은 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;

1
질문은 구체적으로 정책 패턴 이외의 예를 요청했다.
— user2913094

나는이 책에서 정확히이 질문에 왔습니다. 참고로 템플릿 템플릿 매개 변수는 유형 목록 장과 유형 목록을 사용한 클래스 생성 장에도 나타납니다 .
— 빅터

18

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입니다.


"template <class T, template <T> TT> CLayerT"및 "class CLayerCuda : public CLayerT <TensorGPU <float >>"와 같은 방법으로 T를 추론 할 수 있습니까? TT <otherT>가 필요하지 않은 경우
— NicoBerrogorry

절대 안 함 : template <template <class T> class U> class B1 {}; 에서 ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/... 빠른 구글 검색에서
— NicoBerrogorry

12

하위 템플릿 집합에 대한 "인터페이스"를 제공하기 위해 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;

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


일부 코드에는이 정확한 솔루션이 필요했습니다 (감사합니다!). 비록 작동하지만 derived템플릿 인수없이 템플릿 클래스 를 사용 하는 방법을 이해하지 못합니다typedef typename interface<derived, VALUE> type;
— Carlton

@Carlton 채워지는 해당 템플릿 매개 변수가로 정의되어 있기 때문에 기본적으로 작동합니다 template <typename>. 어떤 의미에서 템플릿 매개 변수는 '메타 타입'을 갖는 것으로 생각할 수 있습니다. 템플릿 매개 변수의 일반 메타 typename유형은 일반 유형으로 채워야 함을 의미합니다. template가 필요한 메타 타입 수단 템플릿의 참조로 채워질 수있다. derived하나의 typename메타 타입 매개 변수 를 허용하는 템플리트를 정의 하므로 청구서에 적합하며 여기에서 참조 할 수 있습니다. 말이 되나요?
— Mark McKenna

아직 C ++ 11 typedef입니다. 또한 DERIVED 유형 int과 같은 표준 구문을 사용하여 첫 번째 예제에서 중복 을 피할 수 있습니다 value_type.
— rubenvb 2016 년

이 답변은 실제로 C ++ 11을 목표로하지 않습니다. C ++ 11을 참조 typedef하여 블록 2 에서 문제를 해결할 수 있다고 말 했지만 포인트 2는 유효합니다. 그렇습니다.
— 마크 맥 케나

7

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

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

4

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

2

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

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

2

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

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

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 루프에서의 사용법에 대해 불평하는 컴파일러에 의해 인스턴스화되며 , 후자는 일치하는 유형을 찾을 수 없으므로 템플릿을 전혀 인스턴스화하지 않습니다.

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


1

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

와 같은 템플릿을 통해 버전이 지정된 유형 인 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()함수 를 갖도록 재귀 할 수도 있습니다. 하지만 최신 도구로 처리해야합니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.