¿Cuáles son algunos usos de los parámetros de plantilla de plantilla?

Resuelto Ferruccio asked hace 16 años • 10 respuestas

He visto algunos ejemplos de C++ usando parámetros de plantilla (es decir, plantillas que toman plantillas como parámetros) para realizar un diseño de clases basado en políticas. ¿Qué otros usos tiene esta técnica?

Ferruccio avatar Oct 18 '08 03:10 Ferruccio
Aceptado

Creo que necesitas usar la sintaxis de plantilla para pasar un parámetro cuyo tipo es una plantilla que depende de otra plantilla como esta:

template <template<class> class H, class S>
void f(const H<S> &value) {
}

Aquí Hhay una plantilla, pero quería que esta función se ocupara de todas las especializaciones de H.

NOTA : He estado programando en C++ durante muchos años y solo lo he necesitado una vez. Creo que es una característica que rara vez se necesita (¡por supuesto, útil cuando la necesitas!).

He estado tratando de pensar en buenos ejemplos y, para ser honesto, la mayoría de las veces esto no es necesario, pero inventemos un ejemplo. Supongamos que eso std::vector no tiene typedef value_type.

Entonces, ¿cómo escribirías una función que pueda crear variables del tipo correcto para los elementos del vector? Esto funcionaría.

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

NOTA : std::vectortiene dos parámetros de plantilla, tipo y asignador, por lo que tuvimos que aceptar ambos. Afortunadamente, debido a la deducción de tipos, no será necesario escribir el tipo exacto de forma explícita.

que puedes usar así:

f<std::vector, int>(v); // v is of type std::vector<int> using any allocator

o mejor aún, podemos simplemente usar:

f(v); // everything is deduced, f can deal with a vector of any type!

ACTUALIZACIÓN : Incluso este ejemplo artificial, aunque ilustrativo, ya no es un ejemplo sorprendente debido a la introducción de c++11 auto. Ahora la misma función se puede escribir como:

template <class Cont>
void f(Cont &v) {

    auto temp = v.back();
    v.pop_back();
    // Do some work on temp

    std::cout << temp << std::endl;
}

así es como preferiría escribir este tipo de código.

Evan Teran avatar Oct 17 '2008 20:10 Evan Teran

En realidad, el caso de uso de los parámetros de la plantilla es bastante obvio. Una vez que sepa que C++ stdlib tiene un gran problema al no definir operadores de salida de flujo para tipos de contenedores estándar, procedería a escribir algo como:

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

Entonces te darías cuenta de que el código para vector es el mismo, para forward_list es el mismo, en realidad, incluso para multitud de tipos de mapas sigue siendo el mismo. Esas clases de plantilla no tienen nada en común excepto la metainterfaz/protocolo, y el uso del parámetro de plantilla permite capturar los puntos comunes en todas ellas. Sin embargo, antes de proceder a escribir una plantilla, vale la pena consultar una referencia para recordar que los contenedores de secuencia aceptan 2 argumentos de plantilla: para el tipo de valor y el asignador. Si bien el asignador está predeterminado, aún debemos tener en cuenta su existencia en nuestro operador de plantilla<<:

template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...

Listo, eso funcionará automáticamente para todos los contenedores de secuencias presentes y futuros que cumplan con el protocolo estándar. Para agregar mapas a la mezcla, sería necesario echar un vistazo a la referencia para notar que aceptan 4 parámetros de plantilla, por lo que necesitaríamos otra versión del operador << anterior con un parámetro de plantilla de plantilla de 4 argumentos. También veríamos que std:pair intenta representarse con el operador de 2 argumentos << para los tipos de secuencia que definimos anteriormente, por lo que proporcionaríamos una especialización solo para std::pair.

Por cierto, con C+11 que permite plantillas variadas (y por lo tanto debería permitir argumentos de plantilla de plantilla variadas), sería posible tener un único operador<< para gobernarlos a todos. Por ejemplo:

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

Producción

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 
pfalcon avatar Jan 14 '2013 02:01 pfalcon