답변:
템플릿 템플릿 구문을 사용하여 유형이 다음과 같은 다른 템플릿에 의존하는 템플릿 인 매개 변수를 전달해야한다고 생각합니다.
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;
}
이 유형의 코드를 작성하는 방법을 선호합니다.
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
하지 f<vector<int>>
.
f<vector,int>
의미 f<ATemplate,AType>
, f<vector<int>>
의미f<AType>
실제로 템플릿 템플릿 매개 변수의 사용 사례는 다소 분명합니다. 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
__PRETTY_FUNCTION__
하는데, 무엇보다도 템플릿 매개 변수 설명을 일반 텍스트로보고합니다. clang도 마찬가지입니다. (가시다시피) 가장 편리한 기능입니다.
다음은 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;
불행히도 파생 된 템플릿의 각 인스턴스화에 대해 하나의 파생 된 _ 인터페이스 _ 유형이 필요합니다.
derived
템플릿 인수없이 템플릿 클래스 를 사용 하는 방법을 이해하지 못합니다typedef typename interface<derived, VALUE> type;
template <typename>
. 어떤 의미에서 템플릿 매개 변수는 '메타 타입'을 갖는 것으로 생각할 수 있습니다. 템플릿 매개 변수의 일반 메타 typename
유형은 일반 유형으로 채워야 함을 의미합니다. template
가 필요한 메타 타입 수단 템플릿의 참조로 채워질 수있다. derived
하나의 typename
메타 타입 매개 변수 를 허용하는 템플리트를 정의 하므로 청구서에 적합하며 여기에서 참조 할 수 있습니다. 말이 되나요?
typedef
입니다. 또한 DERIVED 유형 int
과 같은 표준 구문을 사용하여 첫 번째 예제에서 중복 을 피할 수 있습니다 value_type
.
typedef
하여 블록 2 에서 문제를 해결할 수 있다고 말 했지만 포인트 2는 유효합니다. 그렇습니다.
이것은 내가 만난 것입니다.
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)
. 전자의 경우 템플릿은 c
for 루프에서의 사용법에 대해 불평하는 컴파일러에 의해 인스턴스화되며 , 후자는 일치하는 유형을 찾을 수 없으므로 템플릿을 전혀 인스턴스화하지 않습니다.
일반적으로 템플릿 클래스 / 함수가 템플릿 클래스를 템플릿 매개 변수로 처리하도록 설계된 경우 명확하게하는 것이 좋습니다.
버전이 지정된 유형에 사용합니다.
와 같은 템플릿을 통해 버전이 지정된 유형 인 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()
함수 를 갖도록 재귀 할 수도 있습니다. 하지만 최신 도구로 처리해야합니다).