Şablon şablonu parametrelerinin bazı kullanımları nelerdir?


238

İlke tabanlı sınıf tasarımı yapmak için şablon şablonu parametrelerini (şablonlar parametre olarak alan şablonlar) kullanarak C ++ bazı örnekler gördüm. Bu tekniğin başka hangi kullanımları vardır?


4
Diğer yönden (FP, Haskell vb.)
Geldim

Yanıtlar:


197

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, Hbir ş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::vectortü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.


1
F, bir kitaplığın kullanıcısı tarafından tanımlanan bir işlevse, kullanıcının bağımsız değişken olarak std :: allocator <T> 'yi geçirmesi çirkin olur. Ben std :: allocator bağımsız değişkeni olmayan sürüm std :: vector varsayılan parametresini kullanarak çalışmış olması beklenirdi. Bu wrt C ++ 0x üzerinde herhangi bir güncelleme var mı?
amit

Allocator sağlamak zorunda değilsiniz. Önemli olan şablon şablonu parametresinin doğru sayıda argüman üzerinde tanımlanmış olmasıdır. Ancak fonksiyon, " template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
Cinsi

Anlamanın neden olduğunu f<vector,int>ve olmadığını merak ediyorum f<vector<int>>.
bobobobo

2
@bobobobo Bu ikisi farklı şeyler ifade ediyor. f<vector,int>anlamına gelir f<ATemplate,AType>, f<vector<int>>anlamına gelirf<AType>
user362515 12:09

@phaedrus: (çok sonra ...) iyi noktalar, allocator jenerik ve örnek daha açık hale getirmek için örnek geliştirdi :-)
Evan Teran

163

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 

9
Bu, şablon şablon parametrelerinin çok tatlı bir örneğidir, çünkü herkesin uğraşması gereken bir durum gösterir.
Ravenwater

3
Bu benim için C ++ şablonlarında en uyanık cevap. @WhozCraig Şablon genişletme ayrıntılarını nasıl aldınız?
Arun

3
@Arun gcc __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).
WhozCraig

20
Buradaki şablon şablonu parametresi gerçekten herhangi bir değer katmıyor. Sınıf şablonunun herhangi bir örneği olarak normal bir şablon parametresi de kullanabilirsiniz.
David Stone

9
David Stone ile hemfikirim. Burada şablon şablonu parametresinin bir anlamı yoktur. Düz bir şablon (şablon <typename Container>) yapmak çok daha basit ve eşit derecede etkili olacaktır. Bu yazının oldukça eski olduğunu biliyorum, bu yüzden şablon şablonlarıyla ilgili bilgi arayan bu cevaba rastlayan insanlar için sadece 2 sent ekliyorum.
Jim Vargo

67

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;

1
Soru özellikle politika modeli dışındaki örnekler için talep edildi.
user2913094

Bu soruya tam olarak bu kitaptan geldim. Değerli bir nokta, şablon şablonu parametrelerinin Typelist bölümünde ve Typelists ile Sınıf oluşturma bölümünde de görünmesidir .
Victor

18

İş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.


T'nin çıkarılmasını aşağıdaki gibi bir şeyle zorlayabilir misiniz: "template <class T, template <T> TT> CLayerT" ve "class CLayerCuda: public CLayerT <TensorGPU <float>>"? Bir TT'ye ihtiyacınız yoksa <otherT>
NicoBerrogorry

ASLA MIND: şablon <şablon <sınıf T> sınıf U> sınıf B1 {}; dan ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/... hızlı bir google aramasından
NicoBerrogorry

12

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.


Bazı kod için bu kesin çözüm gerekli (teşekkürler!). Çalışmasına rağmen, şablon sınıfının derivedşablon argümanları olmadan nasıl kullanılabileceğini anlamıyorum , yani çizgitypedef typename interface<derived, VALUE> type;
Carlton

@Carlton temel olarak çalışır, çünkü doldurulmakta olan ilgili şablon parametresi a olarak tanımlanır template <typename>. Bir anlamda şablon parametrelerini bir 'metatip' olarak düşünebilirsiniz; bir şablon parametresi için normal metatip, normal bir türle typenamedoldurulması gerektiği anlamına gelir; templateihtiyacı metatype aracı bir şablona referansı ile doldurulması gereklidir. derivedbir typenamemetatipli parametreyi kabul eden bir şablon tanımlar , böylece faturaya uyar ve burada referans verilebilir. Mantıklı olmak?
Mark McKenna

Hala C ++ 11 typedef. Ayrıca, DERIVED türünde inta gibi standart bir yapı kullanarak ilk örneğinizdeki kopyadan kaçınabilirsiniz value_type.
rubenvb

Bu cevap aslında C ++ 11'i hedeflemez; Ben sadece typedefblok 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.
Mark McKenna

7

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

4

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

2

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

2

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 cfor 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.


1

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).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.