Pourquoi ne puis-je pas initialiser une référence dans une liste d'initialiseur avec une initialisation uniforme?
C'est-à-dire, pourquoi cela:
struct S {};
struct T
{
T(S& s) : s{s} {}
S& s;
};
int main()
{
S s;
T t{s};
}
me donner une erreur de compilateur avec GCC 4.7:
test.cpp: In constructor 'T::T(S&)':
test.cpp:5:18: error: invalid initialization of non-const reference of type 'S&' from an rvalue of type '<brace-enclosed initializer list>'
?
pour corriger l'erreur, je dois changer le s{s}
en s(s)
. Cela ne brise-t-il pas le, erm, uniformité d'initialisation uniforme?
EDIT : j'ai essayé avec clang, et clang l'accepte, donc peut-être que c'est un bug GCC?
3 réponses
Oui, c'est un bug . C'est quelque chose de nouveau et a été voté dans le document de travail en février 2012 ( lien ).
Nicol Bolas fait un bon point en ce que gcc est en fait le compilateur conforme selon la norme C++11 approuvée par FDIS parce que les changements au document de travail ont été faits après cela.
je crois que c'est une erreur du compilateur. Les deux paragraphes qui traitent de l'initialisation de référence par liste-initialisation sont (dans n3337):
§8.5.4 /3
Liste d'initialisation d'un objet ou d'une référence de type T est défini comme suit:
dans les autres cas, si la liste des initialisateurs comporte un seul élément de type E et que L'un ou l'autre T n'est pas un le type de référence ou son type de référence est lié à la référence E, l'objet ou la référence est initialisé à partir de cet élément; si une conversion de rétrécissement (voir ci-dessous) est nécessaire pour convertir l'élément en T, le programme est mal formé.
Sinon, si T est un type de référence, une prvalue temporaire du type référencé par T est list-initialisée, et la référence est liée à ce temporaire. [ Note: Comme d'habitude, la liaison échouera et le programme est mal formé si le type de référence est une référence lvalue à un type non-const. - note finale]
le compilateur semble appliquer le dernier paragraphe, alors qu'il devrait appliquer le premier, étant donné que lié à la référence est défini comme
8.5.3 /4
types donnés "cv1 T1" et "cv2 T2", "cv1 T1" est lié à la référence "cv2 T2" si T1 est du même type comme T2, ou T1 est une classe de base de T2.
dans le cas de la question, les types de la référence et de l'initialiseur à l'intérieur de la liste brace-initialization-list sont exactement les mêmes, ce qui signifie que l'initialisation devrait être valide.
dans le projet de la FDIS, l'ordre des paragraphes équivalents a été inversé. L'implication est que L'ébauche de la FDIS (n3290) ne permettait pas de brace-list-initialization of * lvalue*S. D'autre part, en lisant le texte, il semble évident qu'il s'agit d'un bug dans la norme et que l'intention était d'avoir l'ordre de n3337:
Sinon, si T est un type de référence, une valeur temporaire du type référencé par T est initialisée par liste, et la référence est liée à ce temporaire.
autrement, si la liste d'initialiseur a un seul élément, l'objet ou référence est initialisé à partir de cet élément; si une conversion de rétrécissement (voir ci-dessous) est nécessaire pour convertir l'élément en T, le programme est mal formé.
l'ordre dans ce document signifie que puisque tous les types de référence sont traités par la première clause, mentionner référence dans le paragraphe suivant n'aurait aucun sens.
(Note: j'écris cette réponse avec le bénéfice de 2 ans de recul depuis la question originale; et de mettre une partie de l'information des commentaires dans une réponse réelle afin qu'elle soit recherchable).
bien sûr, l'initialisation d'une référence de type S&
avec une référence de type S&
est censé lier directement.
le problème est un défaut dans la norme C++11 et a été adressée par DR1288 . Le texte corrigé apparaît en C++14.
Le Comité a précisé que le texte corrigé est ce qui était prévu pour le C++11, et donc une "conforme compilateur" devrait mettre en œuvre la version corrigée.
g++ 4.8 a suivi le texte publié de la norme C++11; cependant, une fois que ce problème est apparu, g++ 4.9 a implémenté la version corrigée, même avec le commutateur -std=c++11
.
notez que le problème n'est pas limité aux listes d'initialiseur de constructeur non plus, par exemple: S s; S &t{s};
ne fonctionne pas dans g++ 4.8, ni ne fait S s; S &t = s; S &u { t };