2011-10-08 9 views
9
#include <initializer_list> 
#include <utility> 

void foo(std::initializer_list<std::pair<int,int>>) {} 
template <class T> void bar(T) {} 

int main() { 
    foo({{0,1}}); //This works 
    foo({{0,1},{1,2}}); //This works 
    bar({{0,1}}); //This warns 
    bar({{0,1},{1,2}}); //This fails 
    bar(std::initializer_list<std::pair<int,int>>({{0,1},{1,2}})); //This works 
} 

Esto no se compila en gcc 4.5.3, da una advertencia por la línea marcada diciendo deducing ‘T’ as ‘std::initializer_list<std::initializer_list<int> >’ y un error de la línea marcada diciendo no matching function for call to ‘bar(<brace-enclosed initializer list>)’. ¿Por qué puede gcc deducir el tipo de la primera llamada a la barra, pero no la segunda, y hay una manera de arreglar esto que no sea el lanzamiento largo y feo?plantillas no siempre suponer tipos lista de inicialización

+0

posible duplicado de [¿Por qué mi plantilla no acepta una lista de inicializadores] (http://stackoverflow.com/questions/4757614/why-doesnt-my-template-accept-an-initializer-list) - Esa pregunta es básicamente un duplicado. El hecho de que la deducción del tipo se haga en absoluto en una lista de inicializadores es una extensión de g ++. – Omnifarious

+0

@Omnifarious no estoy seguro si estos son engañados. Mi pregunta tenía por objeto solicitar menos detalles que aparentemente esta pregunta. –

Respuesta

18

GCC según C++ 11 no se puede deducir el tipo de las dos primeras llamadas a bar. Advierte porque implementa una extensión de a C++ 11.

La norma dice que cuando un argumento de función en una llamada a una plantilla de función es una { ... } y el parámetro no es initializer_list<X> (opcionalmente un parámetro de referencia), que a continuación, el tipo del parámetro no se puede deducir por el {...}. Si el parámetro es tal como initializer_list<X>, los elementos de la lista de inicializadores se deducen de forma independiente comparando con X, y cada una de las deducciones de los elementos debe coincidir.

template<typename T> 
void f(initializer_list<T>); 

int main() { 
    f({1, 2}); // OK 
    f({1, {2}}); // OK 
    f({{1}, {2}}); // NOT OK 
    f({1, 2.0}); // NOT OK 
} 

En este ejemplo, la primera es OK, y el segundo es bien también porque los primeros rendimientos elemento de tipo int, y el segundo elemento compara {2} contra T - esta deducción no puede producir un constradiction ya que no hace deduce cualquier cosa, por lo tanto, finalmente, la segunda llamada toma T como int. El tercero no puede deducir T por ningún elemento, por lo tanto, NO ES ACEPTABLE. La última llamada produce deducciones contradictorias para dos elementos.

Una manera de hacer este trabajo es utilizar un tipo como el tipo de parámetro

template <class T> void bar(std::initializer_list<std::initializer_list<T>> x) { 
    // ... 
} 

Debo señalar que haciendo std::initializer_list<U>({...}) es peligroso - mejor eliminar los (...) alrededor de los tirantes. En el caso de que suceda a trabajar por accidente, pero tenga en cuenta

std::initializer_list<int> v({1, 2, 3}); 
// oops, now 'v' contains dangling pointers - the backing data array is dead! 

La razón es que ({1, 2, 3}) llama al constructor de copia/movimiento de initializer_list<int> pasándole un temporal initializer_list<int> asociado con el {1, 2, 3}. Ese objeto temporal se destruirá y morirá cuando finalice la inicialización. Cuando ese objeto temporal que está asociado con la lista muera, la matriz de respaldo que contiene los datos también se destruirá (si se elimina el movimiento, vivirá tanto tiempo como "v"; eso es malo, ya que ni siquiera se comportaría) mal garantizado!). Al omitir los parens, v está directamente asociado con la lista, y los datos de la matriz de respaldo se destruyen solo cuando se destruye v.

+0

No entendí el último párrafo. ¿Podrías profundizar un poco más? No veo ninguna diferencia entre 'std :: initializer_list v ({1, 2, 3});' y 'std :: initializer_list v {1, 2, 3};'. Me parecen iguales a mí. – Nawaz

+0

@Nawaz pensé que lo elaboré lo mejor que pude. Si aún tiene preguntas, le recomiendo que abra una nueva pregunta, porque la elaboración necesaria garantizará una nueva respuesta. –

+0

@Nawaz ¿Has abierto una nueva pregunta para el último párrafo? Puede reenviarlo aquí en los comentarios. – SebNag

4

La inicialización uniforme depende de saber qué tipo se está inicializando. {1} podría significar muchas cosas. Cuando se aplica a un int, lo llena con un 1. Cuando se aplica a un std::vector<int>, significa crear un elemento vector, con 1 en el primer elemento. Y así.

Cuando llama a una función de plantilla cuyo tipo no está restringido, no hay información de tipo para una inicialización uniforme. Y sin información de tipo, la inicialización uniforme no puede funcionar.

Por ejemplo:

bar({{0,1}}); 

Se espera que esto sea del tipo std::initializer_list<std::pair<int,int>>. Pero, ¿cómo podría el compilador saber eso? El primer parámetro de bar es una plantilla no restringida; literalmente puede ser cualquier tipo. Cow podría el compilador posiblemente adivinar que querías decir este tipo específico?

Simplemente, no puede. Los compiladores son buenos, pero no son clarividentes. La inicialización uniforme solo puede funcionar en presencia de información de tipo, y las plantillas no restringidas eliminan todo eso.

Por todos los derechos, esa línea no debería haber compilado, según C++ 11. No se puede deducir de qué tipo era {...}, por lo que debería haber fallado. Eso parece un error de GCC o algo así.

+0

Esto no funcionará porque el compilador no tiene idea de qué deducirá 'T' si solo tiene' std :: pair 'y' {1, 2} '. Me gusta cómo describes el problema en la primera parte de tu respuesta. El mismo razonamiento se aplica a cómo obtener 'T' para' par ':) –

+0

@ JohannesSchaub-litb: Es suficiente. Remoto. –

Cuestiones relacionadas