web-dev-qa-db-fra.com

Echec de l'initialisation de la liste imbriquée (vecteur de vecteurs de chaînes)

Ce code:

#include <vector>
#include <string>
#include <iostream>

class MyClass
{
public:
  MyClass(const std::vector<std::vector<std::string>> & v)
  {
    std::cout << "Vector of string vectors size: " << v.size() << "\n";

    for (size_t i = 0; i < v.size(); i++)
      std::cout << "Vector #" << i << " has size " << v[i].size() << "\n";
  }
};

int main()
{
  MyClass({ { "a" } }); // <--- ok
  MyClass({ { "a", "b" } }); // <--- PROBLEM
  MyClass({ { std::string("a"), "b" } }); // <--- ok
  MyClass({ { "a", "b", "c" } }); // <--- ok
  MyClass({ { "a" },{ "c" } }); // <--- ok
  MyClass({ { "a", "b" },{ "c", "d" } }); // <--- ok
}

génère ceci (Visual Studio 2017):

Vector of string vectors size: 1
Vector #0 has size 1
Vector of string vectors size: 4
Vector #0 has size 97
Vector #1 has size 0
Vector #2 has size 0
Vector #3 has size 0
Vector of string vectors size: 1
Vector #0 has size 2
Vector of string vectors size: 1
Vector #0 has size 3
Vector of string vectors size: 2
Vector #0 has size 1
Vector #1 has size 1
Vector of string vectors size: 2
Vector #0 has size 2
Vector #1 has size 2

Cela fonctionne donc bien dans tous les cas sauf dans le cas où nous avons un vecteur d’un vecteur contenant deux chaînes. Cela fonctionne également dans le cas ci-dessus si nous construisons explicitement std :: string à partir de l'un des littéraux de chaîne. Si les deux ne sont que de simples chaînes de caractères, le compilateur semble être "confus" et construit un vecteur de 4 éléments, le premier contenant 97 chaînes. Notez que 97 est le code de caractère de "a".

Je suppose que ma question est la suivante: le compilateur doit-il interpréter cette construction problématique comme je l’attendrais ou ce code erroné initialise-t-il une liste imbriquée?

18
Urmas Rahu

Le vecteur interne dans MyClass({ { "a", "b" } }) est créé à l'aide du constructeur d'intervalle:

template <class InputIterator>
  vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());

Cela se produit parce que { "a", "b" } est interprété non pas comme std::initializer_list<std::string> mais comme une paire de pointeurs bruts.

17
Valery Kopylov

Entrer dans le constructeur incriminé dans le débogueur révèle que VC++ a choisi le constructeur vector<vector<int>> qui prend deux itérateurs (ce sont des const char*s dans ce cas).
C'est-à-dire qu'il traite la construction comme

std::vector<std::vector<std::string>> {"a", "b"}

Ceci, bien sûr, conduit à un comportement indéfini puisque les deux pointeurs n'appartiennent pas au même tableau.

En remarque, g ++ compile les deux

std::vector<std::vector<std::string>> as{{"a", "b"}};
std::vector<std::vector<std::string>> bs{"a", "b"};

mais se bloque misérablement sur le dernier, tandis que le premier se comporte comme prévu.

VC++ compile la construction de variable à double accolade de la manière attendue, donc je soupçonne (espère) qu’il existe un bogue dans VC++.

8
molbdnilo

J'ai trouvé une solution de contournement qui permet d'éviter ce comportement indéfini avec VC++. Vous pouvez définir un second constructeur comme ceci:

MyClass(const std::vector<std::vector<int>> &)
{
}

Ensuite, les lignes de code qui donneraient le problème,

MyClass({ { "a", "b" } }); // <--- PROBLEM

ne compilera plus et vous donnera l'erreur "la résolution de surcharge du constructeur était ambiguë", indiquant le problème Vous pouvez ensuite transtyper le littéral en std :: string pour résoudre le problème.

0
Urmas Rahu