Question

Pour une raison quelconque, je pensais que c ++ 0x autorisé std::initializer_list comme argument de fonction pour les fonctions qui attendent des types qui peuvent être construits à partir de tels, par exemple std::vector. Mais apparemment, cela ne fonctionne 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"} );
}
Était-ce utile?

La solution

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

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

  • Comment et quelle initialisation se fait en général?
  • Comment l'initialisation est-elle utilisée pendant la résolution de surcharge et quel coût a-t-il?

La première question est répondue dans la section 8.5. La deuxième question est répondue dans 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 la 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 le passage d'argument, Retour de la fonction, lançant une exception (15.1), gérer une exception (15.3) et l'initialisation des membres agrégés (8.5.1) est appelée copie-initialisation.
.
.
La sémantique des initialiseurs est la suivante [...]: Si l'initialisateur est une liste de bandes contreventes, l'objet est initialisé par la liste (8.5.4).

Lorsque vous envisagez le candidat function, le compilateur verra une liste d'initialisateur (qui n'a pas encore de type - c'est juste une construction grammaticale!) Comme argument, et un std::vector<std::string> comme le paramètre de function. Pour comprendre quel est le coût de la conversion et si nous boîte 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'initialisateur (8.5.4), ce n'est pas une expression et des règles spéciales s'appliquent pour la convertir en un type de paramètre.

13.3.3.1.5/3:

Sinon, si le paramètre est une classe X et une résolution de surcharge non agrégés par 13.3.1.7 choisit un seul meilleur constructeur de X pour effectuer l'initialisation d'un objet de type X à partir de la liste d'initialisateur de l'argument, la séquence de conversion implicite est un utilisateur. séquence de conversion définie. Les conversions par l'utilisateur sont autorisées pour la conversion des éléments de liste d'initialisateur en types de paramètres du constructeur, sauf comme indiqué dans 13.3.3.1.

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

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

Nous sommes autorisés à convertir la chaîne littérale en std::string, même si cela a besoin d'une conversion définie par l'utilisateur. Cependant, il indique les restrictions d'un autre paragraphe. Qu'est-ce que 13.3.3.1 dire?

13.3.3.1/4, qui est le paragraphe responsable de l'interdiction des conversions définies par l'utilisateur. Nous examinerons uniquement les initialisations de la liste:

Cependant, lorsque vous envisagez l'argument d'une fonction de conversion déposée par l'utilisateur [(ou constructeur)], c'est un candidat par [...] 13.3.1.7 lors du passage de la liste d'initialiseur en tant qu'argument unique ou lorsque la liste d'initialiseur a exactement un élément et une conversion en une classe X ou une référence (peut-être CV-Quali fi ed) x est considérée pour le premier paramètre d'un constructeur de X, ou [...], seules les séquences de conversion standard et les séquences de conversion d'ellipsipse sont autorisées.

Notez qu'il s'agit d'une restriction importante: si ce n'était pas pour cela, ce qui précède peut utiliser la construction 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: il est censé dire "(A ou B) et C" - donc nous sommes restreints seulement Lorsque vous essayez de se convertir par un constructeur de X ayant un paramètre de type X).

Nous sommes délégués à 13.3.1.7 Pour la collecte des constructeurs, nous pouvons utiliser pour effectuer cette conversion. Approchez-vous ce paragraphe du côté général à partir de 8.5 qui nous a délégués à 8.5.4:

8.5.4/1:

L'initialisation de la liste peut se produire dans des contextes d'initialisation directe ou d'initialisation de la copie; La liste-initialisation dans un contexte d'initialisation directe est appelée initialisation de la liste directe et la liste-initialisation dans un contexte d'initialisation de la copie est appelée Initialisation de la liste de copies.

8.5.4/2:

Un constructeur est un Constructeur de liste d'initialisateur Si son premier paramètre est de type std::initializer_list<E> ou référence à éventuellement CV-Quali fi é std::initializer_list<E> Pour un certain type E, et soit il n'y a pas d'autres paramètres, soit tous les autres paramètres ont des arguments par défaut (8.3.6).

8.5.4/3:

La liste-initialisation d'un objet ou référence du type T est définie comme suit: [...] Sinon, si t est un type de classe, les constructeurs sont pris en compte. Si t a un constructeur de liste d'initialisateur, la liste d'arguments se compose de la liste d'initialiseur en tant qu'argument unique; Sinon, la liste des arguments se compose des éléments de la liste d'initialisateur. Les constructeurs applicables sont énumérés (13.3.1.7) et le meilleur est choisi par résolution de surcharge (13.3).

En ce moment, T est le 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'initialisateur grammaticale). Les constructeurs sont énumérés à partir de 13.3.1.7:

. Sinon, la liste des arguments se compose des éléments de la liste d'initialisateur. Pour l'initialisation de la liste de copie, les fonctions candidates sont tous les constructeurs de T. Cependant, si un constructeur explicite est choisi, l'initialisation est mal formée.

Nous ne considérerons que la liste d'initialisateur de std::vector En tant que seul candidat, car nous savons déjà que les autres ne l'emporteront pas ou ne correspondent pas à l'argument. Il a la signature suivante:

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

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

Lorsqu'un argument est une liste d'initialisateur (8.5.4), ce n'est pas une expression et des règles spéciales s'appliquent pour la convertir en un type de paramètre. [...] Si le type de paramètre est std::initializer_list<X> Et tous les éléments de la liste des initialiseurs peuvent être implicitement convertis 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éposée par l'utilisateur Même dans le contexte d'un appel à un constructeur de liste d'initialisateur.

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

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 en fonction des règles d'initialisation d'un objet à partir d'une classe du même type (8.5). (...)

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

Autres conseils

Cela semble fonctionner de cette façon:

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

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

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

Il s'agit d'un bug du compilateur, soit votre compilateur ne prend pas en charge Std :: initializer_list. Testé sur GCC 4.5.1 et il compile bien.

Vous devez spécifier le type de votre initialiseur_list

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

Bonne chance

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top