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


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.