La lista del inicializador de C++11 falla , pero solo en listas de longitud 2


Rastreé un error de registro oscuro al hecho de que las listas inicializadoras de longitud 2 parecen ser un caso especial! ¿Cómo es esto posible?

El código fue compilado con Apple LLVM versión 5.1 (clang-503.0.40), usando CXXFLAGS=-std=c++11 -stdlib=libc++.

#include <stdio.h>

#include <string>
#include <vector>

using namespace std;

typedef vector<string> Strings;

void print(string const& s) {
    printf(s.c_str());
    printf("\n");
}

void print(Strings const& ss, string const& name) {
    print("Test " + name);
    print("Number of strings: " + to_string(ss.size()));
    for (auto& s: ss) {
        auto t = "length = " + to_string(s.size()) + ": " + s;
        print(t);
    }
    print("\n");
}

void test() {
    Strings a{{"hello"}};                  print(a, "a");
    Strings b{{"hello", "there"}};         print(b, "b");
    Strings c{{"hello", "there", "kids"}}; print(c, "c");

    Strings A{"hello"};                    print(A, "A");
    Strings B{"hello", "there"};           print(B, "B");
    Strings C{"hello", "there", "kids"};   print(C, "C");
}

int main() {
    test();
}

Salida:

Test a
Number of strings: 1
length = 5: hello

Test b
Number of strings: 1
length = 8: hello

Test c
Number of strings: 3
length = 5: hello
length = 5: there
length = 4: kids

Test A
Number of strings: 1
length = 5: hello

Test B
Number of strings: 2
length = 5: hello
length = 5: there

Test C
Number of strings: 3
length = 5: hello
length = 5: there
length = 4: kids

También debo agregar que la longitud de la cadena falsa en la prueba b parece ser indeterminada-siempre es mayor que la primera cadena inicializadora, pero ha variado de una más que la longitud de la primera cadena a el total de las longitudes de las dos cadenas en el inicializador.

Author: Filip Roséen - refp, 2014-06-09

2 answers

Introducción

Imagine la siguiente declaración y uso:{[16]]}

struct A {
  A (std::initializer_list<std::string>);
};

A {{"a"          }}; // (A), initialization of 1 string
A {{"a", "b"     }}; // (B), initialization of 1 string << !!
A {{"a", "b", "c"}}; // (C), initialization of 3 strings

En (A) y (C), cada cadena de estilo c está causando la inicialización de uno (1) std::cadena, pero, como ha indicado en su pregunta, (B) difiere.

El compilador ve que es posible construir un std::string con a begin y end-iterador, y sobre parsing statement (B) preferirá dicha construcción en lugar de usar "a" y "b" como inicializadores individuales para dos elementos.

A { std::string { "a", "b" } }; // the compiler's interpretation of (B)


Nota: El tipo de "a" y "b" es char const[2], un tipo que puede decaer implícitamente en un char const*, un tipo puntero que es adecuado para actuar como un iterador que denota begin o end cuando crea una cadena std:: .. pero debemos tener cuidado: están causando undefined-behavior ya que no hay ninguna relación (garantizada) entre los dos punteros al invocar dicho constructor.


Explicación

Cuando invoca un constructor tomando un std:: initializer_list usando llaves dobles {{ a, b, ... }}, hay dos interpretaciones posibles:

  1. Las llaves externas se refieren al constructor en sí, las llaves internas denotan los elementos a tomar parte en el std::initializer_list, o:

  2. Las llaves externas se refieren a std::initializer_list, mientras que las llaves internas denotan la inicialización de un elemento dentro de él.

Se prefiere hacer 2) siempre que eso sea posible, y dado que std::string tiene un constructor que toma dos iteradores, es el que se llama cuando se tiene std::vector<std::string> {{ "hello", "there" }}.

otro ejemplo:

std::vector<std::string> {{"this", "is"}, {"stackoverflow"}}.size (); // yields 2

Solución

No utilice llaves dobles para dicha inicialización.

 76
Author: Filip Roséen - refp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-09 17:19:27

En primer lugar, este es un comportamiento indefinido a menos que me esté perdiendo algo obvio. Ahora déjame explicarte. El vector se construye a partir de una lista inicializadora de cadenas. Sin embargo, esta lista solo contiene una cadena. Esta cadena está formada por el interior {"Hello", "there"}. ¿Cómo? Con el constructor iterador. Esencialmente, for (auto it = "Hello"; it != "there"; ++it) está formando una cadena que contiene Hello\0.

Para un ejemplo simple, vea aquí. Si bien UB es razón suficiente, parecería que el segundo literal se coloca justo después el primero en la memoria. Como bono, haz "Hello", "Hello" y probablemente obtendrás una cadena de longitud 0. Si no entiendes nada aquí, te recomiendo leer La excelente respuesta de Filip .

 20
Author: chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:30