63 votes

Pourquoi emplace_back () n'utilise-t-il pas une initialisation uniforme?

Le code suivant:

#include <vector>

struct S
{
    int x, y;
};

int main()
{
    std::vector<S> v;
    v.emplace_back(0, 0);
}

Donne l'erreur suivante lors de la compilation avec GCC:

In file included from c++/4.7.0/i686-pc-linux-gnu/bits/c++allocator.h:34:0,
                 from c++/4.7.0/bits/allocator.h:48,
                 from c++/4.7.0/vector:62,
                 from test.cpp:1:
c++/4.7.0/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = S; _Args = {int, int}; _Tp = S]':
c++/4.7.0/bits/alloc_traits.h:265:4:   required from 'static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]'
c++/4.7.0/bits/alloc_traits.h:402:4:   required from 'static void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>]'
c++/4.7.0/bits/vector.tcc:97:6:   required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, int}; _Tp = S; _Alloc = std::allocator<S>]'
test.cpp:11:24:   required from here
c++/4.7.0/ext/new_allocator.h:110:4: error: new initializer expression list treated as compound expression [-fpermissive]
c++/4.7.0/ext/new_allocator.h:110:4: error: no matching function for call to 'S::S(int)'
c++/4.7.0/ext/new_allocator.h:110:4: note: candidates are:
test.cpp:3:8: note: S::S()
test.cpp:3:8: note:   candidate expects 0 arguments, 1 provided
test.cpp:3:8: note: constexpr S::S(const S&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'const S&'
test.cpp:3:8: note: constexpr S::S(S&&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'S&&'

Ce qui suggère que vector est l'utilisation de () constructeur de syntaxe pour construire l'élément de la arguments au emplace_back(). Pourquoi ne peut - vector utiliser l' {} uniforme de la syntaxe d'initialisation au lieu de cela, pour faire des exemples comme le travail ci-dessus?

Il me semble qu'il n'y a rien à perdre en utilisant {} (il appelle le constructeur quand il y en a un, mais fonctionne même quand il n'y en a pas), et il serait plus dans l'esprit de C++11 pour utiliser {} - après tout, le point de l'ensemble de l' uniforme de l'initialisation, c'est qu'il est utilisé de manière uniforme - qui est, partout à initialiser des objets.

53voto

Potatoswatter Points 70305

Les grands esprits se rencontrent ;v) . J'ai soumis un rapport de défaut et a suggéré une modification de la norme sur ce sujet.

http://cplusplus.github.com/LWG/lwg-active.html#2089

Aussi, Luc Danton m'a aidé à comprendre la difficulté: Direct vs initialisation uniforme dans std::allocator.

Lorsque le EmplaceConstructible (23.2.1 [conteneur.les exigences.général]/13) exigence est utilisé pour initialiser un objet, direct-initialisation se produit. L'initialisation d'un agrégat ou d' à l'aide d'un std::initializer_list constructeur avec emplace nécessite de nommage la initialisée type et le déplacement temporaire. C'est un résultat de std::allocator::construction à l'aide directe de l'initialisation, pas liste d'initialisation (parfois appelé "initialisation uniforme") la syntaxe.

Modifier std::allocator::construire pour utiliser la liste d'initialisation serait, entre autres choses, de donner la préférence à std::initializer_list surcharges de constructeur, de rupture code valide, un peu intuitive et irrécupérable façon - il n'y aurait aucun moyen pour emplace_back pour accéder à un constructeur préempté par std::initializer_list sans substance réimplanter push_back.

std::vector<std::vector<int>> v;
v.emplace_back(3, 4); // v[0] == {4, 4, 4}, not {3, 4} as in list-initialization

La proposition de compromis est d'utiliser SFINAE avec std::is_constructible, les tests directs-l'initialisation est bien formé. Si is_constructible est faux, alors une alternative std::allocator::construire la surcharge est choisi, et qui utilise la liste d'initialisation. Depuis la liste d'initialisation tombe toujours sur le dos direct-l'initialisation, l'utilisateur verra les messages de diagnostic comme si liste d'initialisation (uniform-initialisation) ont toujours été utilisés, parce que le direct-l'initialisation de la surcharge ne peut pas échouer.

Je vois deux cas de coin qui exposent les lacunes dans ce schéma. L'un se produit lorsque les arguments destinés à std::initializer_list satisfaire un constructeur, comme par exemple essayer de emplace-l'insertion d'une valeur de {3, 4} dans le l'exemple ci-dessus. La solution de contournement est de spécifier explicitement le std::initializer_list type, comme dans v. emplace_back(std::initializer_list(3, 4)). Depuis cela correspond la sémantique comme si std::initializer_list ont été déduites, il semble qu'il y aucun problème ici.

L'autre cas est quand arguments prévu pour l'ensemble de l'initialisation répondre à un constructeur. Depuis les agrégats ne peuvent pas avoir définis par l'utilisateur les constructeurs, cela nécessite que le premier non membre de données de l'agrégat être implicitement converti à partir du type de regroupement, et que la liste d'initialiseur ont un élément. La solution de contournement est de la fourniture d'un initialiseur pour le second membre. Il reste impossible de au-lieu de construire un agrégat avec seulement un non membre de données par la conversion d'un type cabriolet à l'agrégat propre type. Cette semble être un assez petit trou.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X