Pregunta

Mi pregunta de hoy es bastante simple:¿Por qué el compilador no puede inferir parámetros de plantilla a partir de constructores de clases, como puede hacerlo a partir de parámetros de funciones?Por ejemplo, ¿por qué el siguiente código no podría ser válido?

template<typename obj>
class Variable {
      obj data;
      public: Variable(obj d)
              {
                   data = d;
              }
};

int main()
{
    int num = 2;
    Variable var(num); //would be equivalent to Variable<int> var(num),
    return 0;          //but actually a compile error
}

Como digo, entiendo que esto no es válido, entonces mi pregunta es por qué ¿no es así?¿Permitir esto crearía agujeros sintácticos importantes?¿Existe algún caso en el que no se desee esta funcionalidad (donde inferir un tipo causaría problemas)?Solo estoy tratando de entender la lógica detrás de permitir la inferencia de plantillas para funciones, pero no para clases construidas adecuadamente.

¿Fue útil?

Solución

Creo que no es válido porque el constructor no siempre es el único punto de entrada de la clase (estoy hablando de copiar constructor y operador =). Supongamos que está usando su clase como esta:

MyClass m(string s);
MyClass *pm;
*pm = m;

No estoy seguro de si sería tan obvio para el analizador saber qué tipo de plantilla es el MyClass PM;

No estoy seguro de si lo que dije tiene sentido, pero no dude en agregar algún comentario, esa es una pregunta interesante.

C ++ 17

Se acepta que C ++ 17 tendrá una deducción de tipo de los argumentos del constructor.

Ejemplos:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);

Papel aceptado.

Otros consejos

No puedes hacer lo que pides por razones que otras personas han abordado, pero puedes hacer esto:

template<typename T>
class Variable {
    public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
  return Variable<T>(instance);
}

Lo cual, para todas las intenciones y propósitos, es lo mismo que pides. Si te encanta la encapsulación, puedes hacer que la función de miembro estático. Eso es lo que la gente llama nombrada constructor. Entonces, no solo hace lo que quieres, sino que casi se llama lo que quieres: el compilador está inferiendo el parámetro de la plantilla del constructor (nombrado).

NB: Cualquier compilador razonable optimizará el objeto temporal cuando escriba algo como

Variable<T> v = make_variable(instance);

En la edad ilustrada de 2016, con dos nuevos estándares en nuestro haber se hizo esta pregunta y una nueva a la vuelta de la esquina, lo crucial de saber es que Los compiladores que apoyan el estándar C ++ 17 Compile su código como es.

Deducción de plantilla-argumento para plantillas de clase en C ++ 17

Aquí (Cortesía de una edición de Olzhas Zhumabek de la respuesta aceptada) es el documento que detalla los cambios relevantes en el estándar.

Abordar las preocupaciones de otras respuestas

La respuesta actual con calificación superior

Esta respuesta señala que "Copiar constructor y operator="No sabría las especializaciones de plantilla correctas.

Esto no tiene sentido, porque el copy-constructor estándar y operator= solo existen para conocido Tipo de plantilla:

template <typename T>
class MyClass {
    MyClass(const MyClass&) =default;
    ... etc...
};

// usage example modified from the answer
MyClass m(string("blah blah blah"));
MyClass *pm;   // WHAT IS THIS?
*pm = m;

Aquí, como señalé en los comentarios, hay Sin razón por MyClass *pm ser una declaración legal con o sin la nueva forma de inferencia: MyClass no es un tipo (es una plantilla), por lo que no tiene sentido declarar un puntero de tipo MyClass. Aquí hay una forma posible de arreglar el ejemplo:

MyClass m(string("blah blah blah"));
decltype(m) *pm;               // uses type inference!
*pm = m;

Aquí, pm es ya del tipo correcto, y así la inferencia es trivial. Además, es imposible accidentalmente mezcla tipos al llamar al copia constructor:

MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));

Aquí, pm será un puntero a una copia de m. Aquí, MyClass está siendo copiado construido de m—¿Qué es de tipo MyClass<string> (y no del tipo inexistente MyClass). Así, en el punto donde pmSe infiere el tipo es información suficiente para saber que el tipo de plantilla de m, y por lo tanto el tipo de plantilla de pm, es string.

Además, lo siguiente siempre elevar un error de compilación:

MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;

Esto se debe a que la declaración del constructor de copias es no Templado:

MyClass(const MyClass&);

Aquí, el tipo de plantilla del argumento del constructor de copia partidos el tipo de plantilla de la clase en general; es decir, cuando MyClass<string> está instanciado, MyClass<string>::MyClass(const MyClass<string>&); se instancia con él y cuando MyClass<int> está instanciado, MyClass<int>::MyClass(const MyClass<int>&); está instanciado. A menos que se especifique explícitamente o se declare un constructor templatizado, no hay razón para que el compilador instancie MyClass<int>::MyClass(const MyClass<string>&);, que obviamente sería inapropiado.

La respuesta de Cătălin Pitiș

Pitiș da un ejemplo deducir Variable<int> y Variable<double>, luego afirma:

Tengo el mismo nombre de tipo (variable) en el código para dos tipos diferentes (variable y variable). Desde mi punto de vista subjetivo, afecta la legibilidad del código más o menos.

Como se señaló en el ejemplo anterior, Variable en sí mismo es no Un nombre de tipo, a pesar de que la nueva característica lo hace parecer una sintácticamente.

Pitiș luego pregunta qué pasaría si no se da ningún constructor que permita la inferencia apropiada. La respuesta es que no se permite ninguna inferencia, porque la inferencia es activada por el Llamada de constructor. Sin un constructor de llamas, hay sin inferencia.

Esto es similar a preguntar qué versión de foo se deduce aquí:

template <typename T> foo();
foo();

La respuesta es que este código es ilegal, por el motivo indicado.

Respuesta de Msalter

Esta es, por lo que puedo decir, la única respuesta para plantear una preocupación legítima sobre la característica propuesta.

El ejemplo es:

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?

La pregunta clave es, ¿el compilador selecciona el tipo infierno constructor aquí o el Copiar ¿constructor?

Al probar el código, podemos ver que se selecciona el constructor de copias. Para expandir el ejemplo:

Variable var(num);          // infering ctor
Variable var2(var);         // copy ctor
Variable var3(move(var));   // move ctor
// Variable var4(Variable(num));     // compiler error

No estoy seguro de cómo la propuesta y la nueva versión del estándar especifican esto; Parece estar determinado por "Guías de deducción", que son un poco de Standardese que aún no entiendo.

Tampoco estoy seguro de por qué el var4 La deducción es ilegal; El error del compilador de G ++ parece indicar que la declaración se está analizando como una declaración de función.

Todavía falta: hace que el siguiente código sea bastante ambiguo:

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}

Suponiendo que el compilador admite lo que le pidió. Entonces este código es válido:

Variable v1( 10); // Variable<int>

// Some code here

Variable v2( 20.4); // Variable<double>

Ahora, tengo el mismo nombre de tipo (variable) en el código para dos tipos diferentes (variable y variable). Desde mi punto de vista subjetivo, afecta la legibilidad del código más o menos. Tener el mismo nombre de tipo para dos tipos diferentes en el mismo espacio de nombres me parece engañoso.

Actualización posterior:Otra cosa a considerar: especialización de plantillas parcial (o completa).

¿Qué pasa si especializo la variable y no proporciono un constructor como esperas?

Así que yo tuviera:

template<>
class Variable<int>
{
// Provide default constructor only.
};

Entonces tengo el código:

Variable v( 10);

¿Qué debe hacer el compilador? Use la definición de clase de variable genérica para deducir que es variable, luego descubra que la variable no proporciona un constructor de parámetros.

Los estándares C++03 y C++11 no permiten la deducción de argumentos de plantilla a partir de los parámetros pasados ​​al constructor.

Pero hay una propuesta para la "Deducción de parámetros de plantilla para constructores", por lo que pronto obtendrá lo que solicita. Editar:de hecho, esta característica ha sido confirmada para C++17.

Ver: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html y http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html

Muchas clases no dependen de los parámetros del constructor. Solo hay unas pocas clases que solo tienen un constructor y parametrizan en función de los tipos de este constructor.

Si realmente necesita inferencia de plantilla, use una función de ayuda:

template<typename obj>
class Variable 
{
      obj data;
public: 
      Variable(obj d)
      : data(d)
      { }
};

template<typename obj>
inline Variable<obj> makeVariable(const obj& d)
{
    return Variable<obj>(d);
}

La deducción de los tipos se limita a las funciones de plantilla en C ++ actual, pero desde hace mucho tiempo se realiza que la deducción de tipo en otros contextos sería muy útil. Por lo tanto, C ++ 0x auto.

Tiempo exactamente Lo que sugiere que no será posible en C ++ 0x, lo siguiente muestra que puede acercarse bastante:

template <class X>
Variable<typename std::remove_reference<X>::type> MakeVariable(X&& x)
{
    // remove reference required for the case that x is an lvalue
    return Variable<typename std::remove_reference<X>::type>(std::forward(x));
}

void test()
{
    auto v = MakeVariable(2); // v is of type Variable<int>
}

Tiene razón, el compilador podría adivinar fácilmente, pero no está en el estándar o c ++ 0x que yo sé, por lo que tendrá que esperar al menos 10 años más (la tasa de giro fijo de los estándares ISO) antes de que los proveedores de Compiladores agregan esta característica

Veamos el problema con la referencia a una clase con la que todos deberían ser familiarizados - std :: vector.

En primer lugar, un uso muy común de Vector es usar el constructor que no toma parámetros:

vector <int> v;

En este caso, obviamente no se puede realizar una inferencia.

Un segundo uso común es crear un vector precedente:

vector <string> v(100);

Aquí, si se usó la inferencia:

vector v(100);

Obtenemos un vector de INTS, no cuerdas, ¡y presumiblemente no tiene tamaño!

Por último, considere constructores que toman múltiples parámetros, con "inferencia":

vector v( 100, foobar() );      // foobar is some class

¿Qué parámetro debe usarse para la inferencia? Necesitaríamos alguna forma de decirle al compilador que debería ser el segundo.

Con todos estos problemas para una clase tan simple como Vector, es fácil ver por qué no se usa la inferencia.

Hacer que el CTOR sea una plantilla, la variable puede tener solo una forma Pero varios ctors:

class Variable {
      obj data; // let the compiler guess
      public:
      template<typename obj>
      Variable(obj d)
       {
           data = d;
       }
};

int main()
{
    int num = 2;
    Variable var(num);  // Variable::data int?

    float num2 = 2.0f;
    Variable var2(num2);  // Variable::data float?
    return 0;         
}

¿Ver? No podemos tener múltiples variables :: miembros de datos.

Ver La deducción de argumentos de la plantilla de C ++ Para más información sobre esto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top