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.

Foi útil?

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

Papel aceito.

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 é 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 pmO 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top