使用std :: initializer_list构造函数而不会产生歧义?

时间:2017-10-12 06:39:59

标签: c++ variadic-templates c++17 initializer-list type-deduction

我有一个名为Shape的类,可以从任何可迭代的类中初始化,还有一个名为Array的类,它只包含Shape。但是,当我尝试初始化Array时,我收到编译错误,无法解释:

class Shape
{
public:
    template<typename Iterator>
    Shape(Iterator first, Iterator last)
        : m_shape(first, last) {}

    template <typename Iterable>
    Shape(const Iterable& shape)
        : Shape(shape.begin(), shape.end()) {}

    template<typename T>
    Shape(std::initializer_list<T> shape)
        : Shape(shape.begin(), shape.end()) {}

private:
    std::vector<std::size_t> m_shape;
};

class Array
{
public:
    Array(const Shape& shape)
        : m_shape(shape) {}
private:
    Shape m_shape;
};

int main() {
    Shape s{0};       // ok
    Array a1({1, 2}); // ok
    Array a2({0});    // error
}

编译错误出现在Shape的第二个构造函数:

prog.cxx:35:16:   required from here
prog.cxx:14:23: error: request for member ‘begin’ in ‘shape’, which is of non-class type ‘const int’
         : Shape(shape.begin(), shape.end()) {}
                 ~~~~~~^~~~~
prog.cxx:14:38: error: request for member ‘end’ in ‘shape’, which is of non-class type ‘const int’
         : Shape(shape.begin(), shape.end()) {}
                                ~~~~~~^~~

我不明白这里发生了什么。为什么调用Iterable构造函数而不是initializer_list<T>构造函数? Shape构造函数与{0}Array构造函数之间有什么区别?

1 个答案:

答案 0 :(得分:3)

代码格式不正确,但不是因为gcc声称的原因。当你写:

Array a2({0});

我们使用初始化程序Array{0}的所有构造函数进行重载解析。

选项#1是:

Array(Shape const& );

我们将尝试使用Shape复制初始化{0},由于在列表期间优先处理std::initializer_list<int>而最终调用std::initializer_list构造函数模板初始化。

然而,这只是一个选择。选项#2是:

Array(Array&& );

隐式移动构造函数。要检查这是否是候选人,我们会看到我们是否可以使用Array初始化{0},这基本上会重新开始。在下一个图层中,我们看到我们是否可以使用Shape初始化0(因为我们删除了一个图层),而我们可以 - 这是您接受所有的 - 构造函数模板。这确实涉及两个用户定义的转换序列,但that's ok for list-initialization

所以我们有两个选择:

  • 选项#1:{0} --> Shape
  • 选项#2:0 --> Shape --> Array

两者都不比另一个好,所以这个电话是模棱两可的。

简单的解决方法是向构造函数模板添加一个约束,使其实际上是一个范围。无论如何,这通常是一种很好的做法,因为你不希望is_constructible_v<Shape, int>成为真实的......