Argumentos de plantilla predeterminados para plantillas de funciones

187

¿Por qué los argumentos de plantilla predeterminados solo se permiten en plantillas de clase? ¿Por qué no podemos definir un tipo predeterminado en una plantilla de función miembro? Por ejemplo:

struct mycclass {
  template<class T=int>
  void mymember(T* vec) {
    // ...
  }
};

En cambio, C ++ obliga a que los argumentos de plantilla predeterminados solo se permitan en una plantilla de clase.

Arman
fuente
8
+1 Esa es realmente una pregunta difícil.
AraK
1
Para las tres primeras respuestas publicadas, considere este ejemplo: struct S { template <class R = int> R get_me_R() { return R(); } };el parámetro de plantilla no se puede deducir del contexto.
AraK
3
Buena pregunta. Ya han respondido 3 personas para decir que "no tiene sentido", y todos están equivocados en general. Los parámetros de plantilla de función no siempre son deducibles de los parámetros de llamada de función. Por ejemplo, si se les permitiera, podría escribir template <int N = 1> int &increment(int &i) { i += N; return i; }, y luego increment(i);o increment<2>(i);. Tal como están las cosas, tengo que escribir increment<1>(i);.
Steve Jessop
En realidad, los ejemplos míos y de AraK pueden tratarse con sobrecarga. Creo que litb no puede, porque el parámetro de plantilla podría deducirse o especificarse.
Steve Jessop
3
@Steve: El punto y coma que falta es en realidad una nueva sobrecarga del operador EOL para complementar la "Sobrecarga de espacios en blanco C ++" de B. Stavtrup publicada en el Journal of Object-Oriented Programming, 1 de abril de 1992. ( www2.research.att.com/~bs/ papers.html )

Respuestas:

148

Tiene sentido dar argumentos de plantilla predeterminados. Por ejemplo, podría crear una función de clasificación:

template<typename Iterator, 
         typename Comp = std::less<
            typename std::iterator_traits<Iterator>::value_type> >
void sort(Iterator beg, Iterator end, Comp c = Comp()) {
  ...
}

C ++ 0x los presenta a C ++. Vea este informe de defectos de Bjarne Stroustrup: Argumentos de plantilla predeterminados para plantillas de función y lo que dice

La prohibición de argumentos de plantilla predeterminados para plantillas de función es un remanente equivocado del tiempo en que las funciones independientes se trataron como ciudadanos de segunda clase y requirieron que todos los argumentos de plantilla se dedujeran de los argumentos de función en lugar de especificarse.

La restricción obstaculiza seriamente el estilo de programación al hacer innecesariamente que las funciones independientes sean diferentes de las funciones de los miembros, lo que dificulta la escritura de código de estilo STL.

Johannes Schaub - litb
fuente
@Arman, el enlace del informe de defectos contiene los cambios que se realizan en el borrador de trabajo para C ++ 0x y las discusiones. Los argumentos que no se deducen ni se especifican explícitamente se obtienen de los argumentos predeterminados. GCC4.4 admite argumentos predeterminados para plantillas de funciones en modo C ++ 0x.
Johannes Schaub - litb
44
No tiene nada que ver con la pregunta o respuesta, pero Herb Sutter llamó al próximo estándar C ++ 11 después de la reunión del sábado pasado. Lo acabo de leer hoy y tengo ganas de compartir :) hierbasutter.wordpress.com/2010/03/13/…
David Rodríguez - dribeas
y la pregunta de seguimiento obligatoria ... ¿cuándo se espera que llegue a otros compiladores? :)
Jamie Cook
@ JohannesSchaub-litb Tuve el mismo problema: no hay posibilidad de especular el tipo predeterminado en una función de plantilla. Lo resolví mediante una instanciación explícita de la función en el tipo predeterminado ( doubleen mi caso). Quizás no sea "general", pero ¿hay algún inconveniente con esta práctica? Gracias.
JackOLantern
El siguiente código no puede compilar, con errores como error: invalid conversion from ‘int’ to ‘int*’, alguna idea de por qué: `#include <array> #include <algorithm> #include <functional> template <typename Iterator, typename Comp = std :: less <Iterator>> void my_sort ( Iterator beg, Iterator end, Comp c = Comp ()) {std :: sort (beg, end, c); } int main () {std :: array <int, 5> ar {5,2,21,7,4}; my_sort (ar.begin (), ar.end ()); } `
Luke Peterson el
36

Para citar plantillas de C ++: La guía completa (página 207):

Cuando las plantillas se agregaron originalmente al lenguaje C ++, los argumentos explícitos de la plantilla de función no eran una construcción válida. Los argumentos de plantilla de función siempre tuvieron que ser deducibles de la expresión de llamada. Como resultado, no parecía haber una razón convincente para permitir argumentos de plantilla de función predeterminados porque el valor deducido siempre anularía el valor predeterminado.

James McNellis
fuente
simple y conciso :)
InQusitive
17

Hasta ahora, todos los ejemplos ofrecidos de parámetros de plantilla predeterminados para plantillas de función se pueden hacer con sobrecargas.

AraK:

struct S { 
    template <class R = int> R get_me_R() { return R(); } 
};

podría ser:

struct S {
    template <class R> R get_me_R() { return R(); } 
    int get_me_R() { return int(); }
};

Mío:

template <int N = 1> int &increment(int &i) { i += N; return i; }

podría ser:

template <int N> int &increment(int &i) { i += N; return i; }
int &increment(int &i) { return increment<1>(i); }

litb:

template<typename Iterator, typename Comp = std::less<Iterator> >
void sort(Iterator beg, Iterator end, Comp c = Comp())

podría ser:

template<typename Iterator>
void sort(Iterator beg, Iterator end, std::less<Iterator> c = std::less<Iterator>())

template<typename Iterator, typename Comp >
void sort(Iterator beg, Iterator end, Comp c = Comp())

Stroustrup:

template <class T, class U = double>
void f(T t = 0, U u = 0);

Podría ser:

template <typename S, typename T> void f(S s = 0, T t = 0);
template <typename S> void f(S s = 0, double t = 0);

Lo cual probé con el siguiente código:

#include <iostream>
#include <string>
#include <sstream>
#include <ctype.h>

template <typename T> T prettify(T t) { return t; }
std::string prettify(char c) { 
    std::stringstream ss;
    if (isprint((unsigned char)c)) {
        ss << "'" << c << "'";
    } else {
        ss << (int)c;
    }
    return ss.str();
}

template <typename S, typename T> void g(S s, T t){
    std::cout << "f<" << typeid(S).name() << "," << typeid(T).name()
        << ">(" << s << "," << prettify(t) << ")\n";
}


template <typename S, typename T> void f(S s = 0, T t = 0){
    g<S,T>(s,t);
}

template <typename S> void f(S s = 0, double t = 0) {
    g<S,double>(s, t);
}

int main() {
        f(1, 'c');         // f<int,char>(1,'c')
        f(1);              // f<int,double>(1,0)
//        f();               // error: T cannot be deduced
        f<int>();          // f<int,double>(0,0)
        f<int,char>();     // f<int,char>(0,0)
}

La salida impresa coincide con los comentarios para cada llamada a f, y la llamada comentada no se compila como se esperaba.

Así que sospecho que los parámetros de plantilla predeterminados "no son necesarios", pero probablemente solo en el mismo sentido que los argumentos de función predeterminados "no son necesarios". Como indica el informe de defectos de Stroustrup, la adición de parámetros no deducidos fue demasiado tarde para que cualquiera se dé cuenta y / o realmente aprecie que hizo que los valores predeterminados fueran útiles. Por lo tanto, la situación actual se basa en una versión de plantillas de funciones que nunca fue estándar.

Steve Jessop
fuente
@ Steve: ¿Entonces el huevo corría más rápido que el pollo? :) interesante. Gracias.
Arman
1
Probablemente solo una de esas cosas. El proceso de estandarización de C ++ es lento en parte para que las personas tengan tiempo de darse cuenta cuando un cambio crea oportunidades o dificultades en otras partes del estándar. Con suerte, las personas que implementan el borrador del estándar se dan cuenta a medida que avanzan, cuando detectan una contradicción o ambigüedad. Oportunidades para permitir cosas que antes no estaban permitidas, confíe en alguien que quiera escribir el código notando que ya no necesita ser ilegal ...
Steve Jessop
2
Uno más para usted: template<typename T = void> int SomeFunction();. El parámetro de plantilla aquí nunca se usa, y de hecho la función nunca se llama; el único lugar al que se hace referencia es en decltypeo sizeof. El nombre coincide deliberadamente con el nombre de otra función, pero el hecho de que sea una plantilla significa que el compilador preferirá la función libre si existe. Los dos se usan en SFINAE para proporcionar un comportamiento predeterminado donde falta una definición de función.
Tom
4

En Windows, con todas las versiones de Visual Studio puede convertir este error ( C4519 ) en una advertencia o deshabilitarlo así:

#ifdef  _MSC_VER
#pragma warning(1 : 4519) // convert error C4519 to warning
// #pragma warning(disable : 4519) // disable error C4519
#endif

Ver más detalles aquí .

Adi Shavit
fuente
1
Tenga en cuenta que, si bien esto desactiva el mensaje "los argumentos de plantilla predeterminados solo se permiten en una plantilla de clase", en realidad no hace que el proceso de creación de instancias de plantilla utilice el valor proporcionado. Eso requiere VS2013 (o cualquier otro compilador que haya completado el defecto 226 de C ++ 11 "Argumentos de plantilla predeterminados para plantillas de función")
puetzk
1

Lo que uso es el siguiente truco:

Digamos que quieres tener una función como esta:

template <typename E, typename ARR_E = MyArray_t<E> > void doStuff(ARR_E array)
{
    E one(1);
    array.add( one );
}

No se te permitirá, pero lo hago de la siguiente manera:

template <typename T>
struct MyArray_t {
void add(T i) 
{
    // ...
}
};

template <typename E, typename ARR_E = MyArray_t<E> >
class worker {
public:
    /*static - as you wish */ ARR_E* parr_;
    void doStuff(); /* do not make this one static also, MSVC complains */
};

template <typename E, typename ARR_E>
void worker<E, ARR_E>::doStuff()
{
    E one(1);
    parr_->add( one );
}

Entonces de esta manera puedes usarlo así:

MyArray_t<int> my_array;
worker<int> w;
w.parr_ = &arr;
w.doStuff();

Como podemos ver, no es necesario establecer explícitamente el segundo parámetro. Tal vez sea útil para alguien.

alariq
fuente
Esto definitivamente no es una respuesta.
Puppy
@deadmg: ¿puedes explicar por qué? No todos somos gurús de plantillas de C ++. Gracias.
Kev
Esta es una solución alternativa que es bastante ordenada, pero no cubrirá todos los casos que desee. Por ejemplo, ¿cómo aplicarías esto a un constructor?
Tiberiu Savin el
@TiberiuSavin: si lo entendí correctamente, puede hacer lo siguiente: plantilla <nombre de tipo E, nombre de tipo ARR_E> trabajador <E, ARR_E> :: trabajador (ARR_E * parr) {parr_ = parr; }. Y luego úselo así: trabajador <int> w2 (& my_array);
alariq