web-dev-qa-db-fra.com

std :: initializer_list comme argument de fonction

Pour une raison quelconque, je pensais que C++ 0x autorisait std::initializer_list comme argument de fonction pour les fonctions qui attendent des types pouvant être construits à partir de tels, par exemple std::vector. Mais apparemment, ça ne marche pas. Est-ce juste mon compilateur, ou cela ne fonctionnera-t-il jamais? Est-ce à cause de problèmes potentiels de résolution de surcharge?

#include <string>
#include <vector>

void function(std::vector<std::string> vec)
{
}

int main()
{
    // ok
    std::vector<std::string> vec {"hello", "world", "test"};

    // error: could not convert '{"hello", "world", "test"}' to 'std::vector...'
    function( {"hello", "world", "test"} );
}
35
fredoverflow

GCC a un bug. La norme rend cela valide. Voir:

Notez qu'il y a deux côtés de cette

  • Comment et quelle initialisation est faite en général?
  • Comment l'initialisation est-elle utilisée lors de la résolution de la surcharge et quel en est le coût?

La première question reçoit une réponse dans la section 8.5. La réponse à la deuxième question est donnée à la section 13.3. Par exemple, la liaison de référence est gérée à 8.5.3 et 13.3.3.1.4, tandis que l'initialisation de liste est gérée dans 8.5.4 et 13.3.3.1.5.

8.5/14,16:

L'initialisation qui se produit sous la forme

T x = a;

ainsi que dans la transmission d'arguments, la fonction return, le lancement d'une exception (15.1), la gestion d'une exception (15.3) et l'initialisation du membre d'agrégat (8.5.1) est appelée initialisation par copie.
.
.
La sémantique des initialiseurs est la suivante [...]: Si l'initialiseur est une liste initiée-dressée, l'objet est initialisé par liste (8.5.4).

Lorsqu'il considère le candidat function, le compilateur verra une liste d'initialiseurs (qui n'a pas encore de type - c'est juste une construction grammaticale!) En tant qu'argument, et un std::vector<std::string> en tant que paramètre de function. Pour savoir quel est le coût de la conversion et si nous pouvons les convertir dans le contexte de la surcharge, 13.3.3.1/5 dit

13.3.3.1.5/1:

Lorsqu'un argument est une liste d'initialiseur (8.5.4), il ne s'agit pas d'une expression et des règles spéciales s'appliquent à sa conversion en un type de paramètre.

13.3.3.1.5/3:

Sinon, si le paramètre est une classe X non agrégée et que la résolution de surcharge selon 13.3.1.7 choisit un meilleur constructeur de X unique pour effectuer l’initialisation d’un objet de type X à partir de la liste des initialiseurs d’arguments, la séquence de conversion implicite est un utilisateur. séquence de conversion définie. Les conversions définies par l'utilisateur sont autorisées pour la conversion des éléments de la liste d'initialisation en types de paramètres de constructeur, sauf indication contraire dans 13.3.3.1.

La classe non agrégée X est std::vector<std::string> et je vais trouver le meilleur constructeur ci-dessous. La dernière règle nous autorise à utiliser des conversions définies par l'utilisateur dans les cas suivants:

struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }

Nous sommes autorisés à convertir le littéral de chaîne en std::string, même si cela nécessite une conversion définie par l'utilisateur. Cependant, cela renvoie aux restrictions d'un autre paragraphe. Que dit 13.3.3.1?

13.3.3.1/4, qui est le paragraphe responsable d'interdire plusieurs conversions définies par l'utilisateur. Nous ne regarderons que les initialisations de liste:

Toutefois, lors de l'examen de l'argument d'une fonction de conversion définie par l'utilisateur [(ou constructeur)] candidate au [...] 13.3.1.7 lors du passage de la liste d'initialisation sous forme d'argument unique ou lorsque la liste d'initialisation contient exactement un élément et une conversion en classe X ou une référence à (éventuellement qualifiée de cv) X est considérée comme le premier paramètre d'un constructeur de X, ou [...], seules les séquences de conversion standard et les séquences de conversion Ellipsis sont autorisées.

Notez qu'il s'agit d'une restriction importante: sans cette option, le concepteur de copie ci-dessus peut utiliser le constructeur de copie pour établir une séquence de conversion tout aussi bien, et l'initialisation serait ambiguë. (remarquez la confusion potentielle de "A ou B et C" dans cette règle: elle est censée dire "(A ou B) et C" - nous sommes donc restreints seulement lorsque nous essayons de convertir par un constructeur de X ayant un paramètre de type X).

Nous sommes délégués à 13.3.1.7 pour collecter les constructeurs que nous pouvons utiliser pour effectuer cette conversion. Abordons ce paragraphe du côté général à partir de 8.5 qui nous a délégué à 8.5.4:

8.5.4/1:

L'initialisation de liste peut avoir lieu dans des contextes d'initialisation directe ou d'initialisation de copie; l'initialisation de liste dans un contexte d'initialisation directe est appelée initialisation de liste directe et l'initialisation de liste dans un contexte d'initialisation de copie est appelée initialisation de liste de copie.

8.5.4/2:

Un constructeur est un constructeur de la liste d'initialisation si son premier paramètre est de type std::initializer_list<E> ou une référence à std::initializer_list<E> éventuellement qualifiée de cv pour un type E, et qu'il n'y a pas d'autres paramètres ou que tous les autres paramètres ont des arguments par défaut (8.3.6).

8.5.4/3:

L'initialisation de liste d'un objet ou d'une référence de type T se définit comme suit: [...] Sinon, si T est un type de classe, les constructeurs sont pris en compte. Si T a un constructeur de liste d'initialiseurs, la liste d'arguments est constituée de la liste d'initialisateurs sous la forme d'un argument unique; sinon, la liste d'arguments est constituée des éléments de la liste d'initialisation. Les constructeurs applicables sont énumérés (13.3.1.7) et le meilleur est choisi par résolution de surcharge (13.3).

T correspond actuellement au type de classe std::vector<std::string>. Nous avons un argument (qui n'a pas encore de type! Nous sommes juste dans le contexte d'avoir une liste d'initialiseurs grammaticaux). Les constructeurs sont énumérés à partir de 13.3.1.7:

[...] Si T a un constructeur de liste d'initialiseurs (8.5.4), la liste d'arguments est constituée de la liste d'initialisateurs sous la forme d'un argument unique; sinon, la liste d'arguments est constituée des éléments de la liste d'initialisation. Pour l’initialisation par copie de liste, les fonctions candidates sont toutes les constructeurs de T. Toutefois, si un constructeur explicite est choisi, l’initialisation est mal formée.

Nous ne considérerons que la liste d'initialisation de std::vector comme seul candidat, car nous savons déjà que les autres ne gagneront pas ou ne correspondront pas à l'argument. Il porte la signature suivante:

vector(initializer_list<std::string>, const Allocator& = Allocator());

Maintenant, les règles de conversion d'une liste d'initialiseur en un std::initializer_list<T>pour catégoriser le coût de la conversion argument/paramètre) sont énumérées dans 13.3.3.1.5:

Lorsqu'un argument est une liste d'initialiseur (8.5.4), il ne s'agit pas d'une expression et des règles spéciales s'appliquent à sa conversion en un type de paramètre. [...] Si le type de paramètre est std::initializer_list<X> et que tous les éléments de la liste d'initialisation peuvent être convertis implicitement en X, la séquence de conversion implicite est la pire conversion nécessaire pour convertir un élément de la liste en X. _ (Cette conversion peut être une conversion définie par l'utilisateurmême dans le contexte d'un appel à un constructeur de liste d'initialiseurs.

Maintenant, la liste d'initialisation sera convertie avec succès et la séquence de conversion est une conversion définie par l'utilisateur (de char const[N] à std::string). Comment cela est fait est encore détaillé à 8.5.4:

Sinon, si T est une spécialisation de std::initializer_list<E>, un objet initializer_list est construit comme décrit ci-dessous et utilisé pour initialiser l'objet conformément aux règles d'initialisation d'un objet d'une classe du même type (8.5). (...)

Voir 8.5.4/4 comment cette dernière étape est faite :)

54

Cela semble fonctionner de cette façon:

function( {std::string("hello"), std::string("world"), std::string("test")} );

C'est peut-être un bogue du compilateur, mais vous demandez peut-être trop de conversions implicites.

3
UncleBens

De prime abord, je ne suis pas sûr, mais je soupçonne que ce qui se passe ici est que la conversion en initializer_list est une conversion, et la conversion en vecteur est une autre conversion. Si c'est le cas, vous dépassez la limite d'une seule conversion implicite ...

2
Jerry Coffin

C'est soit un bogue du compilateur, soit votre compilateur ne supporte pas std :: initializer_list. Testé sur GCC 4.5.1 et il compile bien.

1
Ricky65

Vous devez spécifier le type de votre initializer_list

function(std::initializer_list<std::string>{"hello", "world", "test"} );

Bonne chance

0
fnc12