Por que não inferir o parâmetro do modelo do construtor?
-
13-09-2019 - |
Pergunta
minha pergunta de hoje é muito simples:por que não pode o compilador inferir parâmetros do modelo a partir de construtores de classe, tanto quanto ele pode fazer a partir de parâmetros de uma função?Por exemplo, por que não poderia o código a seguir é válida:
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 eu disse, eu entendo que isso não é válido, então a minha pergunta é por não é?Seria, permitindo criar qualquer grande sintática buracos?Existe uma instância onde não se deseja que esta funcionalidade (onde se inferir um tipo poderia causar problemas)?Eu só estou tentando entender a lógica por trás permitindo que o modelo de inferência para as funções, mas não para o convenientemente construído classes.
Solução
Eu acho que não é válido porque o construtor nem sempre é o único ponto de entrada da classe (estou falando sobre o construtor e operador de cópia =). Então, suponha que você esteja usando sua classe como esta:
MyClass m(string s);
MyClass *pm;
*pm = m;
Não tenho certeza se seria tão óbvio para o analisador saber qual tipo de modelo é o MyClass PM;
Não tenho certeza se o que eu disse faz sentido, mas fique à vontade para acrescentar algum comentário, essa é uma pergunta interessante.
C ++ 17
Aceita -se que o C ++ 17 terá dedução do tipo de argumentos do construtor.
Exemplos:
std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);
Outras dicas
Você não pode fazer o que pede por motivos que outras pessoas abordaram, mas pode fazer isso:
template<typename T>
class Variable {
public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
return Variable<T>(instance);
}
O que para todas as intenções e propósitos é a mesma coisa que você pede. Se você gosta de encapsulamento, pode tornar Make_variable uma função estática de membro. É isso que as pessoas chamam de construtor chamado de construtor. Portanto, não apenas faz o que você deseja, mas é quase chamado o que você deseja: o compilador está inferindo o parâmetro de modelo do construtor (nomeado).
NB: Qualquer compilador razoável otimizará o objeto temporário quando você escrever algo como
Variable<T> v = make_variable(instance);
Na era iluminada de 2016, com dois novos padrões em nosso correio, já que essa pergunta foi feita e uma nova ao virar da esquina, a coisa crucial a saber é que compiladores que suportam o padrão C ++ 17 Compile seu código como está.
Dedução do argumento de modelo para modelos de aula em C ++ 17
Aqui (Cortesia de uma edição de Olzhas Zhumabek da resposta aceita) é o artigo detalhando as alterações relevantes no padrão.
Abordando preocupações de outras respostas
A resposta de melhor classificação atual
Esta resposta aponta que "copiar construtor e operator=
"Não saberia as especializações corretas do modelo.
Isso é um absurdo, porque o construtor de cópias padrão e operator=
só existem para conhecido Tipo de modelo:
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;
Aqui, como observei nos comentários, há Sem motivo por MyClass *pm
ser uma declaração legal com ou sem a nova forma de inferência: MyClass
não é um tipo (é um modelo), por isso não faz sentido declarar um ponteiro do tipo MyClass
. Aqui está uma maneira possível de corrigir o exemplo:
MyClass m(string("blah blah blah"));
decltype(m) *pm; // uses type inference!
*pm = m;
Aqui, pm
é já do tipo correto e, portanto, a inferência é trivial. Além disso, é impossível acidentalmente misturar Tipe ao chamar o constitui-consultor:
MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));
Aqui, pm
será um ponteiro para uma cópia de m
. Aqui, MyClass
está sendo construído de cópia de m
- que é do tipo MyClass<string>
(e não do tipo inexistente MyClass
). Assim, no ponto em que pm
O tipo é inferido lá é informações suficientes para saber que o tipo de modelo de m
, e, portanto, o tipo de modelo de pm
, é string
.
Além disso, o seguinte irá sempre Levante um erro de compilação:
MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;
Isso ocorre porque a declaração do construtor de cópias é não modificado:
MyClass(const MyClass&);
Aqui, o tipo de modelo do argumento de cópia-construtor fósforos o tipo de modelo da classe em geral; ou seja, quando MyClass<string>
é instanciado, MyClass<string>::MyClass(const MyClass<string>&);
é instanciado por isso e quando MyClass<int>
é instanciado, MyClass<int>::MyClass(const MyClass<int>&);
é instanciado. A menos que seja explicitamente especificado ou um construtor templatizado seja declarado, não há razão para o compilador instanciar instanciar MyClass<int>::MyClass(const MyClass<string>&);
, o que obviamente seria inapropriado.
A resposta de Cătălin Pitiș
Pitiș dá um exemplo deduzindo Variable<int>
e Variable<double>
, então afirma:
Eu tenho o mesmo nome de tipo (variável) no código para dois tipos diferentes (variável e variável). Do meu ponto de vista subjetivo, afeta praticamente a legibilidade do código.
Como observado no exemplo anterior, Variable
em si é não Um nome de tipo, mesmo que o novo recurso faça com que pareça sintaticamente.
Pitiș então pergunta o que aconteceria se não for dado o construtor que permitiria a inferência apropriada. A resposta é que nenhuma inferência é permitida, porque a inferência é desencadeada pelo Chamada de construtor. Sem uma chamada construtora, existe Sem inferência.
Isso é semelhante a perguntar qual versão de foo
é deduzido aqui:
template <typename T> foo();
foo();
A resposta é que esse código é ilegal, pelo motivo declarado.
Resposta de msalter
Isso é, até onde eu sei, a única resposta para criar uma preocupação legítima com o recurso proposto.
O exemplo é:
Variable var(num); // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?
A questão principal é: o compilador seleciona o tipo de tipo construtor aqui ou o cópia de construtor?
Tentando o código, podemos ver que o construtor de cópias está selecionado. Para expandir o exemplo:
Variable var(num); // infering ctor
Variable var2(var); // copy ctor
Variable var3(move(var)); // move ctor
// Variable var4(Variable(num)); // compiler error
Não tenho certeza de como a proposta e a nova versão do padrão especificam isso; Parece ser determinado por "Guias de dedução", que são um novo pedaço de Standardese que ainda não entendo.
Também não sei por que o var4
A dedução é ilegal; O erro do compilador de G ++ parece indicar que a instrução está sendo analisada como uma declaração de função.
Ainda falta: torna o seguinte código bastante ambíguo:
int main()
{
int num = 2;
Variable var(num); // If equivalent to Variable<int> var(num),
Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}
Supondo que o compilador suporta o que você pediu. Então este código é válido:
Variable v1( 10); // Variable<int>
// Some code here
Variable v2( 20.4); // Variable<double>
Agora, eu tenho o mesmo nome de tipo (variável) no código para dois tipos diferentes (variável e variável). Do meu ponto de vista subjetivo, afeta praticamente a legibilidade do código. Ter o mesmo tipo de nome para dois tipos diferentes no mesmo namespace parece enganoso para mim.
Atualização posterior:Outra coisa a considerar: especialização parcial (ou completa) do modelo.
E se eu especializar a variável e não fornecer nenhum construtor como você espera?
Então eu teria:
template<>
class Variable<int>
{
// Provide default constructor only.
};
Então eu tenho o código:
Variable v( 10);
O que o compilador deve fazer? Use definição de classe variável genérica para deduzir que é variável e descubra que a variável não fornece um construtor de parâmetros?
O C++03 e o padrão C++11 não permite a dedução do argumento de modelo de parâmetros passados para o constuructor.
Mas há uma proposta de "Modelo de parâmetro de dedução para os construtores" assim, você pode obter o que você está pedindo em breve. Editar:de fato, esse recurso foi confirmado para C++17.
Veja: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html e http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
Muitas classes não dependem dos parâmetros do construtor. Existem apenas algumas classes que possuem apenas um construtor e parametrizam com base no (s) tipo (s) desse construtor.
Se você realmente precisar de inferência de modelo, use uma função auxiliar:
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);
}
A dedução de tipos é limitada a funções de modelo no C ++ atual, mas há muito tempo se percebe que a dedução do tipo em outros contextos seria muito útil. Daí C ++ 0x's auto
.
Enquanto exatamente O que você sugere não será possível em C ++ 0x, os seguintes mostram que você pode chegar bem perto:
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>
}
Você está certo, o compilador pode adivinhar facilmente, mas não está no padrão ou C ++ 0x, tanto quanto eu sei, então você terá que esperar pelo menos 10 anos (padrões ISO Fixed Turn Take) antes que os fornecedores de complicadores adicionem esse recurso
Vejamos o problema com referência a uma classe que todos devem estar familiarizados com - std :: vetor.
Em primeiro lugar, um uso muito comum do vetor é usar o construtor que não leva parâmetros:
vector <int> v;
Nesse caso, obviamente nenhuma inferência pode ser realizada.
Um segundo uso comum é criar um vetor de tamanho prévio:
vector <string> v(100);
Aqui, se a inferência foi usada:
vector v(100);
Temos um vetor de INTs, não cordas, e presumivelmente não é dimensionado!
Por fim, considere os construtores que tomam vários parâmetros - com "inferência":
vector v( 100, foobar() ); // foobar is some class
Qual parâmetro deve ser usado para inferência? Precisamos de alguma maneira de dizer ao compilador que deveria ser o segundo.
Com todos esses problemas para uma classe tão simples quanto o vetor, é fácil ver por que a inferência não é usada.
Fazer o construtor de um modelo a Variável pode ter apenas um formulário mas vários 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;
}
Vê?Nós não podemos ter vários Variável::membros de dados.
See The C++ Template Argument Deduction for more info on this.