为什么用结构初始化数组需要指定结构名称

Why does intializing array with structs requires specifying the struct name

为什么这段代码会产生编译时错误?

#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

您缺少一对括号。

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 }
}};

C++ 17 标准(11.6.1 聚合)中的以下引述解释了该行为

12 Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the elements of a subaggregate; it is erroneous for there to be more initializer-clauses than elements. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the elements of the subaggregate; any remaining initializer-clauses are left to initialize the next element of the aggregate of which the current subaggregate is an element.

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 },
}
};

然后编译器将所有子列表视为内部子聚合的元素,并将此子列表用作 class 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] 的初始值设定项。所以根据引用,大括号可能会被省略。