简体   繁体   English

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

[英]Using std::initializer_list constructor without creating ambiguities?

I have a class called Shape , which can be initialized from any iterable, and a class called Array , which simply contains a Shape . 我有一个名为Shape的类,它可以从任何可迭代的类中初始化,还有一个名为Array的类,它只包含一个Shape However, I'm getting a compile error I can't explain when I try to initialize an Array : 但是,当我尝试初始化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
}

The compilation error appears on the second constructor of Shape : 编译错误出现在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()) {}
                                ~~~~~~^~~

I don't understand what is happening here. 我不明白这里发生了什么。 Why is the Iterable constructor called instead of the initializer_list<T> constructor? 为什么调用Iterable函数而不是initializer_list<T>构造函数? What's the difference between the Shape constructor with {0} and the Array constructor? Shape构造函数与{0}Array构造函数之间有什么区别?

The code is ill-formed, but not for the reason gcc claims it is. 代码格式不正确,但不是因为gcc声称的原因。 When you write: 当你写:

Array a2({0});

We do overload resolution over all the constructors of Array using the initializer {0} . 我们使用初始化器{0}Array所有构造函数进行重载解析。

Option #1 is: 选项#1是:

Array(Shape const& );

on which we would recurse into attempting to copy-initialize Shape with {0} which ends up invoking the std::initializer_list<int> constructor template due to preferential treatment of std::initializer_list during list-initialization. 由于在列表std::initializer_list期间优先处理std::initializer_list ,我们将在其中尝试使用{0}复制初始化Shape ,最终调用std::initializer_list<int>构造函数模板。

However, that's just one option. 但是,这只是一个选择。 Option #2 is: 选项#2是:

Array(Array&& );

The implicit move constructor. 隐式移动构造函数。 To check if that's a candidate, we see if we can initialize Array with {0} , which basically starts over again. 为了检查这是否是候选者,我们看看是否可以使用{0}初始化Array ,它基本上重新开始。 In this next layer, we see if we can initialize Shape with 0 (since we're one layer removed), and we can - that's your accept-all-the-things constructor template. 在下一层中,我们看看是否可以用0初始化Shape (因为我们删除了一层),我们可以 - 这是你接受所有东西的构造函数模板。 This does involve two user-defined conversion sequences, but that's ok for list-initialization . 这确实涉及两个用户定义的转换序列,但这对于列表初始化是可以的

So we have two options: 所以我们有两个选择:

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

Neither is better than the other, so the call is ambiguous. 两者都不比另一个好,所以这个电话是模棱两可的。


The easy fix is to add a constraint to your constructor template such that it actually is a range. 简单的解决方法是向构造函数模板添加一个约束,使其实际上是一个范围。 This is generally good practice anyway, since you don't want is_constructible_v<Shape, int> to be true... 无论如何,这通常都是很好的做法,因为你不希望is_constructible_v<Shape, int>是真的......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM