Список инициализаторов С++ 11 не работает, но только в списках длины 2

Я отследил неясную ошибку ведения журнала, связанную с тем, что списки инициализаторов длины 2 представляют собой особый случай! Как это возможно?

Код был скомпилирован с помощью Apple LLVM версии 5.1 (clang-503.0.40) с использованием 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();
}

Выход:

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

Я также должен добавить, что длина поддельной строки в тесте b кажется неопределенной - она ​​всегда больше, чем первая строка инициализатора, но варьируется от длины первой строки на единицу до суммы длин двух строк. в инициализаторе.


person Tom Swirly    schedule 09.06.2014    source источник
comment
Почему двойные скобки?   -  person chris    schedule 09.06.2014
comment
Извините, я должен был дать понять, что проблема возникает только с двойными фигурными скобками. Одинарные скобки верны, но почему я получаю несоответствие, когда у меня есть двойные скобки? Я расширил пример, включив для сравнения правильную инициализацию с использованием одинарных фигурных скобок.   -  person Tom Swirly    schedule 09.06.2014
comment
Двойные фигурные скобки должны быть допустимыми, поскольку внешние фигурные скобки вызывают обычный поиск конструктора, а внутренние фигурные скобки соответствуют параметру конструктора std::initializer_list. Но это определенно странно.   -  person Potatoswatter    schedule 09.06.2014
comment
@Potatoswatter, вот о чем я думаю, но да.   -  person chris    schedule 09.06.2014
comment
Подождите, длина в тесте b 6 или 8? Ревизия изменила его.   -  person Potatoswatter    schedule 09.06.2014
comment
Есть ли причина, по которой вы используете cstdio вместо iostream?   -  person Rubens    schedule 09.06.2014
comment
Я добавил комментарий, чтобы указать, что результат длины строки в тесте b кажется переменным! хотя, по-видимому, не на данной компиляции. Я использую stdio, потому что в исходном коде я вызываю некоторую библиотечную подпрограмму в библиотеке, которую я выделил, которая выглядит как stdio. Я полагаю, что получу те же результаты с С++ IO.   -  person Tom Swirly    schedule 09.06.2014
comment
@TomSwirly Действительно. И да, у меня такие же результаты при компиляции с g++ (GCC) 4.9.0.   -  person Rubens    schedule 09.06.2014
comment
я бы исследовал взаимодействие с векторными конструкторами, особенно с итератором и итератором   -  person Cheers and hth. - Alf    schedule 09.06.2014
comment
Понятно. Позвольте мне сформировать ответ   -  person chris    schedule 09.06.2014
comment
Он падает с VIsual C++, что свидетельствует о работе UB, что свидетельствует о взаимодействии конструктора.   -  person Cheers and hth. - Alf    schedule 09.06.2014
comment
Еще более странно то, что программа выдает исключение, когда вы создаете экземпляр Strings в main, но оно исчезает, когда вы закомментируете вызовы print() в test(). Я думаю, что происходит какой-то UB. -- coliru.stacked-crooked.com/a/bf9b59160c6f46b0   -  person 0x499602D2    schedule 09.06.2014
comment
Отправили отчет о дефекте.   -  person Potatoswatter    schedule 09.06.2014
comment
@Potatoswatter, интересно. Я почти уверен, что когда-то тестировал его с двумя массивами разного размера.   -  person chris    schedule 09.06.2014
comment
связан, но не дубликат: stackoverflow.com/q/19847960/819272   -  person TemplateRex    schedule 09.06.2014


Ответы (2)


Вступление

Представьте себе следующее объявление и использование:

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

В (A) и (C) каждая строка в стиле c вызывает инициализацию одного (1) std:: string, но, как вы указали в своем вопросе, (B) отличается.

Компилятор видит, что можно создать std::string с помощью begin- и end-iterator, а также после синтаксического анализа оператора (B) такая конструкция предпочтительнее использования "a" и "b" в качестве отдельных инициализаторов для двух элементов.

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

Примечание. Тип "a" и "b" — это char const[2], тип, который может неявно распадаться на char const*, тип указателя, который подходит для действия как итератор, обозначающий либо begin< /em> или end при создании std::string.. но мы должны быть осторожны: мы вызываем неопределенное поведение, так как нет (гарантированной) связи между двумя указателями при вызове указанного конструктора.< / суп>


Объяснение

Когда вы вызываете конструктор, принимающий std::initializer_list с помощью двойных фигурных скобок {{ a, b, ... }}, возможны две интерпретации:

  1. Внешние фигурные скобки относятся к самому конструктору, внутренние фигурные скобки обозначают элементы, которые будут участвовать в std::initializer_list, или:

  2. Внешние фигурные скобки относятся к std::initializer_list, тогда как внутренние скобки обозначают инициализацию элемента внутри него.

Предпочтительно делать 2) всякий раз, когда это возможно, и поскольку std::string имеет конструктор, принимающий два итератора, именно он вызывается, когда у вас есть std::vector<std::string> {{ "hello", "there" }}.

Дополнительный пример:

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

Решение

Не используйте двойные фигурные скобки для такой инициализации.

person Filip Roséen - refp    schedule 09.06.2014
comment
Спасибо за уточнение, не то чтобы вы еще не поняли это до того, как я опубликовал :) - person chris; 09.06.2014
comment
@chris Требуется время, чтобы исправить более приятное форматирование, и, как всегда, это делает меня несколько медленнее, чем все остальные: P - person Filip Roséen - refp; 09.06.2014
comment
Ну да, я, вероятно, хорошо отформатировал бы свой пост после того, как дал ответ, но теперь нет особого смысла копировать ваш пост: p Вместо этого я просто дал ссылку на ваш, хотя, надеюсь, ваш является принятым. @tom, подсказка подсказка - person chris; 09.06.2014
comment
Тип "a" - это НЕ const char*, это const char[2], который свободно конвертируется в const char*. - person Mooing Duck; 09.06.2014
comment
@MooingDuck очень хорошая мысль, спасибо. довольны новой формулировкой? - person Filip Roséen - refp; 09.06.2014
comment
Да, теперь точнее - person Mooing Duck; 09.06.2014

Прежде всего, это неопределенное поведение, если только я не упустил что-то очевидное. Теперь позвольте мне объяснить. Вектор строится из списка строк инициализатора. Однако этот список содержит только одну строку. Эта строка формируется внутренним {"Hello", "there"}. Как? С конструктором итератора. По сути, for (auto it = "Hello"; it != "there"; ++it) формирует строку, содержащую Hello\0.

Простой пример см. здесь. В то время как UB является достаточной причиной, кажется, что второй литерал помещается сразу после первого в памяти. В качестве бонуса выполните "Hello", "Hello", и вы вероятно получите строку длины 0. Если вы ничего здесь не понимаете, я рекомендую прочитать отличный ответ Филиппа.

person chris    schedule 09.06.2014
comment
… и если компилятор решит поместить "there" по адресу меньше, чем "Hello", вы получите сбой. - person Potatoswatter; 09.06.2014
comment
Ха! Это должно было быть неопределенное поведение. Но подождите, почему не бесконечный цикл? Ответ: потому что по прихоти компилятора две строки расположились в памяти более-менее рядом! - person Tom Swirly; 09.06.2014
comment
@Potatoswatter, да, это действительно интересное явление. Я заметил, что выполнение "Hello", "Hello" дало строку длины 0. - person chris; 09.06.2014
comment
Я сейчас пойду за едой. Я не собираюсь отмечать это правильно, пока не вернусь, просто чтобы побудить вас отредактировать его, но, ну, я почти уверен, что вы правы... :-) - person Tom Swirly; 09.06.2014
comment
@chris: зависит от настроек компилятора, они могут иметь нулевую длину или любую другую длину - person Mooing Duck; 09.06.2014
comment
@MooingDuck, конечно. Логично было бы повторно использовать массив, но в лучшем случае все это шатко. Мой ответ более ясен, чем комментарий. - person chris; 09.06.2014
comment
@chris 0 от повторного использования и 8 от выравнивания слов объяснимы и не так интересны. Получить 4 за Hello и o было бы круто (хотя и маловероятно), но самое интересное и часть того, что делает C++ таким забавным, — это сам ответ. - person Nick; 10.06.2014