Почему инициализация массива со структурами требует указания имени структуры - PullRequest
2 голосов
/ 18 февраля 2020

Почему этот код вызывает ошибку времени компиляции?

#include <array>
#include <cstdint>
#include <string_view>

using namespace std::string_view_literals;

enum class my_enum : std::size_t {
    first = 0,
    second,
    third,

    COUNT,
};

struct my_enum_str_pair {
    std::string_view str;
    my_enum command;
};

constexpr 
std::array<my_enum_str_pair, static_cast<std::size_t>(my_enum::COUNT)> 
my_enum_str_pairs = {
    { "first"sv, my_enum::first },
    { "second"sv, my_enum::second }, // error: excess elements in struct initializer
    { "third"sv, my_enum::third },
};

Но если мы изменим его на

...

constexpr 
std::array<my_enum_str_pair, static_cast<std::size_t>(my_enum::COUNT)> 
my_enum_str_pairs = {
    my_enum_str_pair{ "first"sv, my_enum::first },
    { "second"sv, my_enum::second },
    { "third"sv, my_enum::third },
};

Является ли компиляция просто прекрасной?

https://godbolt.org/z/pi2j8a

Ответы [ 2 ]

3 голосов
/ 18 февраля 2020

Поведение объясняется следующей цитатой из C ++ 17 Standard (11.6.1 Aggregates)

12 Брекеты могут быть исключены из списка инициализаторов следующим образом . Если список инициализаторов начинается с левой фигурной скобки, то последующий разделенный запятыми список предложений инициализаторов инициализирует элементы субагрегата; ошибочно иметь больше инициализаторов, чем элементов. Если, однако, список инициализатора для субагрегата не начинается с левой фигурной скобки, то для инициализации элементов субагрегата берется только достаточно предложений инициализатора из списка; любые оставшиеся предложения инициализатора оставляются для инициализации следующего элемента агрегата, элементом которого является текущий субагрегат.

Std :: array является агрегатом, который включает другой агрегат.

constexpr 
std::array<my_enum_str_pair, static_cast<std::size_t>(my_enum::COUNT)> 
my_enum_str_pairs = {
    { "first"sv, my_enum::first },
    { "second"sv, my_enum::second }, // error: excess elements in struct initializer
    { "third"sv, my_enum::third },
};

Таким образом, компилятор считает первую левую скобку в первом инициализаторе

{ "first"sv, my_enum::first },

^^^

В качестве инициализатора внутренней субагрегата (что обычно это массив) совокупности std::array. После этого списка он встречает второй список

{ "second"sv, my_enum::second }

, но не находит другого подобъекта std::array. Таким образом, компилятор выдает ошибку.

Если вы заключите эти списки в фигурные скобки

constexpr 
std::array<my_enum_str_pair, static_cast<std::size_t>(my_enum::COUNT)> 
my_enum_str_pairs = {
{
    { "first"sv, my_enum::first },
    { "second"sv, my_enum::second }, // error: excess elements in struct initializer
    { "third"sv, my_enum::third },
}
};

, то компилятор рассматривает весь подсписок как элементы внутренней субагрегата и использует этот субэлемент. list как инициализаторы конструктора класса std :: pair.

Во втором случае левые фигурные скобки

constexpr 
std::array<my_enum_str_pair, static_cast<std::size_t>(my_enum::COUNT)> 
my_enum_str_pairs = {
                   ^^^^ 
    my_enum_str_pair{ "first"sv, my_enum::first },
    { "second"sv, my_enum::second },
    { "third"sv, my_enum::third },
};

запускает список инициализаторов внутренней субагрегата, исключая еще одну фигурную скобку

Для большей ясности рассмотрим три примера одной и той же программы с разными подходами к инициализации.

Первая программа

#include <iostream>

struct Int
{
    Int( int x ) : x( x ) {}
    int x;
};

template <size_t N>
struct Array
{
    Int a[N];
};

int main() 
{
    Array<3> a = { { 1 }, { 2 }, { 3 } };

    return 0;
}

Компилятор выдаст ошибка, потому что первая открытая скобка перед 1

    Array<3> a = { { 1 }, { 2 }, { 3 } };
                  ^^^

рассматривается компилятором как список инициализатора внутреннего агрегата Int a[N].

Если добавить еще одну пару скобок типа

#include <iostream>

struct Int
{
    Int( int x ) : x( x ) {}
    int x;
};

template <size_t N>
struct Array
{
    Int a[N];
};

int main() 
{
    Array<3> a = { { { 1 }, { 2 }, { 3 } } };

    return 0;
}

Тогда в этом случае первые открытые скобки будут следующими

    Array<3> a = { { { 1 }, { 2 }, { 3 } } };
                  ^^^

, и элементы внутри этих скобок будут считаться инициализаторами Внутренний агрегат Int a[N].

В третьей программе

#include <iostream>

struct Int
{
    Int( int x ) : x( x ) {}
    int x;
};

template <size_t N>
struct Array
{
    Int a[N];
};

int main() 
{
    Array<3> a = { Int{ 1 }, { 2 }, { 3 } };

    return 0;
}

список инициализаторов не начинается с открытой фигурной скобки для внутреннего агрегата Int a [N]. Он начинается с выражения приведения

Int{ 1 }

Таким образом, все остальные элементы рассматриваются компилятором как инициализаторы внутреннего агрегата Int a [N]. Таким образом, согласно цитате, скобки могут быть исключены.

1 голос
/ 18 февраля 2020

Вам не хватает пары скобок.

constexpr 
std::array<my_enum_str_pair, static_cast<std::size_t>(my_enum::COUNT)> 
my_enum_str_pairs = 
{{
    { "first"sv, my_enum::first },
    { "second"sv, my_enum::second }, // error: excess elements in struct initializer
    { "third"sv, my_enum::third }
}};
...