3

我正在制作一个使用其大小模板化的数组的小类。这是一些代码...

.hpp

template <size_t N>
class KeyCombinationListener
{
public:
    KeyCombinationListener(
        const std::array<sf::Keyboard::Key, N>& sequence,
        std::function<void (void)> fn
        );

private:
    std::array<sf::Keyboard::Key, N>  combo;
    std::function<void (void)>  callback;
};

.cc

template <size_t N>
KeyCombinationListener<N>::KeyCombinationListener(
    const array<sf::Keyboard::Key, N>& sequence, function<void (void)> fn
    ) : combo(sequence), progress{begin(combo)}, callback{fn}
{

}

在构造函数的成员初始化中,我不能combo{sequence}用作初始化器,因为它只接受sf::Keyboard::Key类型。如果它要求一个 ,这是有道理的initializer_list,但这对我来说似乎很奇怪。对于其他标准容器,我可以使用 {} 表示法调用复制构造函数。这是一个怪癖std::array吗?或者也许是我的clang中的一个错误?

以防万一,这是我的clang版本:

Debian clang version 3.5.0-10 (tags/RELEASE_350/final) (based on LLVM 3.5.0)
Target: x86_64-pc-linux-gnu
Thread model: posix
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/4.9
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/4.9.2
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/4.9
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/4.9.2
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/4.9
Candidate multilib: .;@m64
Selected multilib: .;@m64
4

1 回答 1

3

您在 C++ 中遇到了一个缺陷:从单个元素进行列表初始化。C++11 和 C++14 国际标准中规定的行为令人惊讶。我将在下面参考 C++14。

的模板实例化std::array是聚合[array.overview]/2。因此,当从一个花括号初始化列表std::array初始化对象时,聚合初始化将不加选择地执行初始化器的数量[dcl.init.list]/3.1。由于某些构造的要求(例如来自一对迭代器),其他容器类不能被聚合。

聚合初始化(可能递归地)初始化来自初始化程序的数据成员。在您的情况下,它将尝试std::array<sf::Keyboard::Key, N>从初始化程序sequence(属于同一类型)初始化第一个数据成员。对于std::array我知道的所有实现,第一个数据成员std::array是 C 风格的数组。然后,列表初始化将尝试从原始初始化程序初始化该数组的第一个元素:sequence

例子:

struct aggregate
{
    int m[2];
};

aggregate x = {0, 1};
assert(x.m[0] == 0 && x.m[1] == 1);

aggregate y{x}; // error: cannot convert `aggregate` to `int`

最后一行中的初始化将尝试从y.m[0]初始化x


CWG 问题 1467描述了这个问题和一个相关问题,即在没有初始化程序时进行列表初始化。提议的决议为列表初始化引入了一个(又一个)特殊情况,涵盖了 OP 中的问题。引用最近的 github 草案,[dcl.init.list]/3.1

如果T是一个类类型并且初始化器列表有一个类型为cv U的元素,其中UisT或一个派生自 的类T,则从该元素初始化对象(对于复制列表初始化,通过复制初始化,对于复制列表初始化,通过直接初始化直接列表初始化)。

最近的草案中的聚合初始化具有较低的“优先级”(3.3),即只有在不满足上述条件时才会执行。


即使在 C++11 模式下,最新版本的 g++ (5.0) 和 clang++ (3.7.0) 也实现了建议的解决方案。

于 2015-04-03T23:23:55.503 回答