Yanıtlar:
Ben böyle bir başka şablona bağlı bir şablon olan bir parametre geçirmek için şablon şablonu sözdizimi kullanmanız gerektiğini düşünüyorum:
template <template<class> class H, class S>
void f(const H<S> &value) {
}
İşte, H
bir şablon, ama ben bu fonksiyonun tüm uzmanlıklarını ele almak istedim H
.
NOT : Yıllardır c ++ programlıyorum ve sadece bir kez buna ihtiyacım var. Nadiren ihtiyaç duyulan bir özellik olduğunu düşünüyorum (elbette ihtiyacınız olduğunda kullanışlı!).
İyi örnekleri düşünmeye ve dürüst olmak gerekirse, çoğu zaman bu gerekli değil, ama bir örnek verelim. Bir tane std::vector
olmadığını varsayalım typedef value_type
.
Peki vektörler elemanları için doğru tipte değişkenler yaratabilen bir fonksiyon nasıl yazarsınız? Bu işe yarar.
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;
}
NOT : std::vector
tür ve ayırıcı olmak üzere iki şablon parametresi vardır, bu yüzden ikisini de kabul etmek zorunda kaldık. Neyse ki, tür kesinti nedeniyle, kesin türü açıkça yazmamız gerekmeyecek.
Bu şekilde kullanabilirsiniz:
f<std::vector, int>(v); // v is of type std::vector<int> using any allocator
ya da daha iyisi, biz sadece kullanabilirsiniz:
f(v); // everything is deduced, f can deal with a vector of any type!
GÜNCELLEME : Bu çelişkili örnek bile açıklayıcı olsa da, c ++ 11'in tanıtımı nedeniyle şaşırtıcı bir örnek değildir auto
. Şimdi aynı işlev şu şekilde yazılabilir:
template <class Cont>
void f(Cont &v) {
auto temp = v.back();
v.pop_back();
// Do some work on temp
std::cout << temp << std::endl;
}
Ben de bu tür bir kod yazmayı tercih ederim.
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
ve olmadığını merak ediyorum f<vector<int>>
.
f<vector,int>
anlamına gelir f<ATemplate,AType>
, f<vector<int>>
anlamına gelirf<AType>
Aslında, şablon şablonu parametreleri için usecase oldukça açıktır. C ++ stdlib'in standart kapsayıcı türleri için akış çıktı işleçlerini tanımlamama konusunda boşluk deliği olduğunu öğrendikten sonra, aşağıdaki gibi bir şey yazmaya devam edersiniz:
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;
}
Sonra vektör kodunun aynı olduğunu anlarsınız, ileri_listesi aynıdır, aslında, çok sayıda harita türü için bile hala aynıdır. Bu şablon sınıflarının meta arabirim / protokol dışında ortak bir yanı yoktur ve şablon şablonu parametresini kullanmak hepsinde ortaklığın yakalanmasını sağlar. Yine de bir şablon yazmaya devam etmeden önce, dizi kaplarının değer türü ve ayırıcı için 2 şablon bağımsız değişkeni kabul ettiğini hatırlamak için bir referansı kontrol etmeye değer. Ayırıcı varsayılan olarak ayarlanmış olsa da, hala şablon operatörünüzdeki varlığını hesaba katmalıyız <<:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
Voila, standart protokole bağlı olan mevcut ve gelecekteki tüm dizi kapları için otomatik olarak çalışacaktır. Karışıma haritalar eklemek için, 4 şablon parametresini kabul ettiklerini belirtmek referans olarak bir göz atması gerekir, bu nedenle 4-arg şablon şablonu parametresi ile yukarıdaki operatörün << başka bir sürümüne ihtiyacımız var. Ayrıca std: pair'in daha önce tanımladığımız dizi türleri için 2-arg operatörü << ile oluşturulmaya çalışıldığını da görüyoruz, bu yüzden sadece std :: pair için bir uzmanlık sağlayacağız.
Btw, değişken şablonlara izin veren (ve dolayısıyla değişken şablon şablonu argümanlarına izin vermesi gereken) C + 11 ile, hepsini yönetmek için tek bir operatöre << sahip olmak mümkün olacaktır. Örneğin:
#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;
}
Çıktı
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__
, diğer şeylerin yanı sıra, şablon parametre açıklamalarını düz metin olarak bildiren adlı bir makroyu destekler . clang da yapar. Bazen en kullanışlı özellik (gördüğünüz gibi).
Andrei Alexandrescu'nun 'Modern C ++ Tasarımı - Uygulanan Genel Programlama ve Tasarım Desenleri'nden alınan basit bir örnek :
Politika modelini uygulamak için şablon şablonu parametrelerine sahip bir sınıf kullanır:
// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
...
};
Şöyle açıklıyor: Genellikle, ana makine sınıfı, ilke sınıfının şablon argümanını zaten bilir veya kolayca çıkarabilir. Yukarıdaki örnekte, WidgetManager her zaman Widget türündeki nesneleri yönetir, bu nedenle kullanıcının CreationPolicy örneğinde Widget'ı tekrar belirtmesini gerektirir ve bu durumda kitaplık kodu ilkeleri belirtmek için şablon şablonu parametrelerini kullanabilir.
Sonuç, istemci kodunun 'WidgetManager'ı daha zarif bir şekilde kullanabilmesidir:
typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
Şablon şablonu argümanlarından yoksun bir tanımın gerekli olması daha hantal ve hataya meyilli bir yol yerine:
typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
İşte CUDA Konvolüsyon sinir ağı kütüphanemden bir başka pratik örnek . Aşağıdaki sınıf şablonu var:
template <class T> class Tensor
bu aslında n-boyutlu matris manipülasyonunu uygular. Ayrıca bir alt sınıf şablonu da vardır:
template <class T> class TensorGPU : public Tensor<T>
aynı işlevi uygular ancak GPU'da. Her iki şablon da float, double, int, vb.Gibi tüm temel türlerle çalışabilir. Ayrıca bir sınıf şablonum var (basitleştirilmiş):
template <template <class> class TT, class T> class CLayerT: public Layer<TT<T> >
{
TT<T> weights;
TT<T> inputs;
TT<int> connection_matrix;
}
Burada şablon şablonu sözdiziminin olmasının nedeni, sınıfın uygulanmasını bildirebilmemdir.
class CLayerCuda: public CLayerT<TensorGPU, float>
hem float hem de GPU'da hem ağırlıklara hem de girdilere sahip olacak, ancak bağlantı_matrisi CPU'da (TT = Tensör belirterek) veya GPU'da (TT = TensorGPU belirterek) her zaman int olacaktır.
Diyelim ki bir dizi alt şablon için bir "arayüz" sağlamak için CRTP kullandığınızı; ve hem üst hem de alt öğe diğer şablon bağımsız değişkenlerinde parametriktir:
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;
Aslında her iki şablonda da belirtilen aynı tür parametresi olan 'int' in çoğaltılmasına dikkat edin. Bu çoğaltmayı önlemek için DERIVED için bir şablon şablonu kullanabilirsiniz:
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;
Diğer şablon parametrelerini türetilmiş şablona doğrudan sağladığınızı unutmayın ; "arayüz" hala onları alıyor.
Bu ayrıca türetilmiş şablondan erişilebilecek tür parametrelerine bağlı olan "arabirim" de typedef'ler oluşturmanıza olanak sağlar.
Yukarıdaki typedef çalışmıyor, çünkü belirtilmemiş bir şablona typedef yapamıyorsunuz. Ancak bu çalışır (ve C ++ 11 şablon typedefs için yerel desteğe sahiptir):
template <typename VALUE>
struct derived_interface_type {
typedef typename interface<derived, VALUE> type;
};
typedef typename derived_interface_type<int>::type derived_t;
Henüz öğrenmediğim başka bir hile yoksa, türetilmiş şablonun her örneği için maalesef bir interinter_type türüne ihtiyacınız vardır.
derived
şablon argümanları olmadan nasıl kullanılabileceğini anlamıyorum , yani çizgitypedef typename interface<derived, VALUE> type;
template <typename>
. Bir anlamda şablon parametrelerini bir 'metatip' olarak düşünebilirsiniz; bir şablon parametresi için normal metatip, normal bir türle typename
doldurulması gerektiği anlamına gelir; template
ihtiyacı metatype aracı bir şablona referansı ile doldurulması gereklidir. derived
bir typename
metatipli parametreyi kabul eden bir şablon tanımlar , böylece faturaya uyar ve burada referans verilebilir. Mantıklı olmak?
typedef
. Ayrıca, DERIVED türünde int
a gibi standart bir yapı kullanarak ilk örneğinizdeki kopyadan kaçınabilirsiniz value_type
.
typedef
blok 2'den sorunu alabilirsiniz demek için C ++ 11 referans . Ama nokta 2 geçerlidir bence ... evet, muhtemelen aynı şeyi yapmak için daha basit bir yol olacaktır.
Ben de karşılaştım budur:
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)>>>>>>>>
{
};
Çözülebilir:
template<class A>
class B
{
A& a;
};
template< template<class> class B>
class A
{
B<A> b;
};
class AInstance : A<B> //happy
{
};
veya (çalışma kodu):
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 tarafından sağlanan varyasyon şablonları ile çözümde, varyasyon uzmanlık açgözlü doğası nedeniyle std :: map için ostream operatörü uzmanlaşmak zor buldum. İşte benim için çalışan küçük bir düzeltme:
#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;
}
İşte yeni kullandığım bir şeyden genelleştirilmiş olan. Çok basit bir örnek olduğu için gönderiyorum ve varsayılan argümanlarla birlikte pratik bir kullanım durumu gösteriyor:
#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;
};
Kodunuzun okunabilirliğini artırır, ekstra tip güvenlik sağlar ve derleyici çalışmalarından tasarruf sağlar.
Bir kabın her öğesini yazdırmak istediğinizi varsayalım, şablon şablonu parametresi olmadan aşağıdaki kodu kullanabilirsiniz
template <typename T> void print_container(const T& c)
{
for (const auto& v : c)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
veya şablon şablonu parametresi ile
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';
}
Bir tamsayı geçtiğinizi varsayalım print_container(3)
. Önceki durumda, şablon c
for döngüsünün kullanımından şikayet edecek derleyici tarafından başlatılır , ikincisi şablonu eşleştirmez, çünkü eşleşen tür bulunmaz.
Genel olarak konuşursak, şablon sınıfınız / işleviniz şablon sınıfını şablon parametresi olarak işlemek üzere tasarlanmışsa, bunu netleştirmek daha iyidir.
Sürümlü türler için kullanıyorum.
Bir şablon aracılığıyla sürümlendirilmiş bir türünüz varsa MyType<version>
, sürüm numarasını yakalayabileceğiniz bir işlev yazabilirsiniz:
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)
{
...
}
}
Böylece, her tür için aşırı yükleme yerine, geçirilen türün sürümüne bağlı olarak farklı şeyler yapabilirsiniz. İçeri giren MyType<Version>
ve geri dönen dönüşüm işlevleriniz de olabilirMyType<Version+1>
genel bir şekilde ve hatta ToNewest()
eski sürümlerden bir türün en son sürümünü döndüren bir işleve sahip olmalarını sağlayabilirsiniz (bir süre önce depolanmış olabilecek günlükler için çok yararlıdır) ancak bugünün en yeni aracıyla işlenmesi gerekir).