简体   繁体   English

C ++ 11构造函数重载解析和Initialiser_lists:clang ++和g ++意见不同

[英]C++11 constructor overload resolution and initialiser_lists: clang++ and g++ disagree

I have a small piece of C++11 code which g++ (4.7 or 4.8) refuses to compile claiming that the call to constructor for B2 b2a(x, {P(y)}) is ambiguous. 我有一小段C ++ 11代码,g ++(4.7或4.8)拒绝编译,声称对B2 b2a(x,{P(y)})的构造函数的调用不明确。 Clang++ is happy with that code, but refuses to compile B2 b2b(x, {{P(y)}}) which g++ is perfectly happy to compile! Clang ++对此代码感到满意,但是拒绝编译b ++ b2b2b(x,{{P(y)}}),而g ++非常乐意编译!

Both compilers are perfectly happy with the B1 constructor with either {...} or {{...}} as an argument. 两个编译器都对使用{...}或{{...}}作为参数的B1构造函数感到非常满意。 Can any C++ language lawyer explain which compiler is correct (if either) and what is going on? 任何C ++语言律师都可以解释哪个编译器正确(如果有)以及正在发生什么情况? Code below: 代码如下:

#include <initializer_list>

using namespace std;

class Y {};
class X;

template<class T> class P {
public:
    P(T);
};

template<class T> class A {
public:
    A(initializer_list<T>);
};

class B1 {
public:
    B1(const X&, const Y &);
    B1(const X&, const A<Y> &);
};

class B2 {
public:
    B2(const X &, const P<Y> &);
    B2(const X &, const A<P<Y>> &);
};

int f(const X &x, const Y y) {
    B1 b1a(x, {y});
    B1 b1b(x, {{y}});
    B2 b2a(x, {P<Y>(y)});
    B2 b2b(x, {{P<Y>(y)}});
    return 0;
}

and the compiler errors, clang: 和编译器错误,c:

$ clang++ -stdlib=libc++ -std=c++11 test-initialiser-list-4.cc -o test.o -c 
test-initialiser-list-4.cc:32:6: error: call to constructor of 'B2' is ambiguous
  B2 b2(x, {{P<Y>(y)}});
     ^  ~~~~~~~~~~~~~~
test-initialiser-list-4.cc:26:5: note: candidate constructor
    B2(const X &, const P<Y> &);
    ^
test-initialiser-list-4.cc:27:5: note: candidate constructor
    B2(const X &, const A<P<Y>> &);
    ^

g++: g ++:

test-initialiser-list-4.cc: In function 'int f(const X&, Y)':
test-initialiser-list-4.cc:32:21: error: call of overloaded 'B2(const X&, <brace-enclosed initializer list>)' is ambiguous
   B2 b2(x, {P<Y>(y)});
                     ^
test-initialiser-list-4.cc:32:21: note: candidates are:
test-initialiser-list-4.cc:27:5: note: B2::B2(const X&, const A<P<Y> >&)
     B2(const X &, const A<P<Y>> &);
     ^
test-initialiser-list-4.cc:26:5: note: B2::B2(const X&, const P<Y>&)
     B2(const X &, const P<Y> &);
     ^

This smells like an interaction between uniform initialisation, initialiser list syntax and function overloading with templated arguments (which I know g++ is fairly stringent about), but I'm not enough of a standards lawyer to be able to unpack what should be the correct behaviour here! 这闻起来像是统一初始化,初始化程序列表语法和带有模板参数的函数重载之间的相互作用(我知道g ++相当严格),但是我还不足以使标准律师能够解开应该是正确行为的标准律师。这里!

First code, then what I think should happen. 首先是代码,然后是我认为应该发生的事情。 (In what follows, I will ignore the first parameter, since we are interested only into the second parameter. The first one is always an exact match in your example). (在接下来的内容中,我将忽略第一个参数,因为我们仅对第二个参数感兴趣。第一个参数始终与您的示例完全匹配)。 Please note that the rules are currently in flux in the spec, so I wouldn't say that one or the other compiler has a bug. 请注意,规则目前在规范中处于不断变化中,因此我不会说一个或另一个编译器存在错误。

B1 b1a(x, {y});

This code cannot call the const Y& constructor in C++11, because Y is an aggregate and Y has no data member of type Y (of course) or something else initializable by it (this is something ugly, and is worked on to be fixed - the C++14 CD doesn't have wording for this yet, so I am not sure whether final C++14 will contain this fix). 这段代码无法在C ++ 11中调用const Y&构造函数,因为Y是一个聚合,并且Y没有类型Y数据成员(当然)或由它初始化的其他东西(这很丑陋,并且被认为是已修复-C ++ 14 CD尚无此措辞,因此我不确定最终的C ++ 14是否会包含此修复程序。

The constructor with the const A<Y>& parameter can be called - {y} will be taken as the argument to the constructor of A<Y> , and will initialize that constructor's std::initializer_list<Y> . 可以将带有const A<Y>&参数的构造const A<Y>&称为- {y}作为A<Y>构造函数的参数,并将初始化该构造函数的std::initializer_list<Y>

Hence - second constructor called successfully . 因此, 第二个构造函数成功调用

B1 b1b(x, {{y}});

Here, the basically same argument counts counts for the constructor with the const Y& parameter. 在这里,使用const Y&参数的构造函数的计数基本相同。

For the constructor with parameter type const A<Y>& , it is a bit more complicated. 对于参数类型为const A<Y>&的构造const A<Y>& ,它要复杂一些。 The rule for conversion cost in overload resolution computing the cost of initializing an std::initializer_list<T> requires every element of the braced list to be convertible to T . 重载解析中转换成本的规则,计算初始化std::initializer_list<T>的成本时,需要将支撑列表中的每个元素都转换为T However we before said that {y} cannot be converted to Y (as it is an aggregate). 但是我们之前说过{y}不能转换为Y (因为它是一个聚合)。 It is now important to know whether std::initializer_list<T> is an aggregate or not. 现在,重要的是要知道std::initializer_list<T>是否为聚合。 Frankly, I have no idea whether or not it must be considered to be an aggregate according to the Standard library clauses. 坦白说,根据标准库条款,我不知道是否必须将其视为一个汇总。

If we take it to be a non-aggregate, then we would be considering the copy constructor of std::initializer_list<Y> , which however again would trigger the exact same sequence of tests (leading to "infinite recursion" in overload resolution checking). 如果我们将其视为非聚合,则将考虑使用std::initializer_list<Y>的副本构造std::initializer_list<Y> ,但是它将再次触发完全相同的测试序列(导致过载解析检查中的“无限递归” )。 Since this is rather weird and non-implementable, I don't think any implementation takes this path. 由于这很奇怪且不可实现,因此我认为任何实现都不会采用这种方式。

If we take std::initializer_list to be an aggregate, we will be saying "nope, no conversion found" (see the above aggregates-issue). 如果我们将std::initializer_list视为一个聚合,我们将说“没有,找不到转换”(请参阅​​上面的aggregates-issue)。 In that case since we cannot call the initializer constructor with the single initializer list as a whole, {{y}} will be split up into multiple arguments, and the constructor(s) of A<Y> will be taking each of those separately. 在那种情况下,由于我们不能用一个整体的初始值设定项列表来调用初始值设定项构造函数,因此{{y}}将被拆分为多个参数,而A<Y>的构造函数将把每个参数分开。 Hence, in this case, we would end up with {y} initializing a std::initializer_list<Y> as the single parameter - which is perfectly fine and work like a charm. 因此,在这种情况下,我们将以{y}结束,将std::initializer_list<Y>为单个参数-很好,可以像超级按钮一样工作。

So under the assumption that std::initializer_list<T> is an aggregate, this is fine and call the second constructor successfully . 因此,在std::initializer_list<T>是一个聚合的假设下,这很好,可以成功调用第二个构造函数

 B2 b2a(x, {P<Y>(y)}); 

In this case and the next case, we don't have the aggregate issue like above with Y anymore, since P<Y> has a user-provided constructor. 在这种情况下以及下一种情况下,由于P<Y>具有用户提供的构造函数,因此不再像上面的Y那样存在聚合问题。

For the P<Y> parameter constructor, that parameter will be initialized by {P<Y> object} . 对于P<Y>参数构造函数,该参数将由{P<Y> object}初始化。 As P<Y> has no initializer lists, the list will be split up into individual arguments and call the move-constructor of P<Y> with an rvalue object of P<Y> . 由于P<Y>没有初始化程序列表,因此该列表将被拆分为各个参数,并使用Rvalue对象P<Y>调用P<Y>的move-constructor。

For the A<P<Y>> parameter constructor, it is the same as the above case with A<Y> initialized by {y} : Since std::initializer_list<P<Y>> can be initialized by {P<Y> object} , the argument list is not split, and hence the braces are used to initializer that constructor'S std::initializer_list<T> . 对于A<P<Y>>参数构造函数,它与上述用{y}初始化A<Y>情况相同:因为std::initializer_list<P<Y>>可以通过{P<Y> object} ,则不会拆分参数列表,因此大括号用于初始化构造函数的std::initializer_list<T>

Now, both constructors work fine. 现在,两个构造函数都可以正常工作。 They are acting like overloaded functions here, and their second parameter in both cases requires a user defined conversion. 它们在这里的作用就像重载函数,在两种情况下,它们的第二个参数都需要用户定义的转换。 User defined conversion sequences can only be compared if in both cases the same conversion function or constructor is used - not the case here. 只有在两种情况下都使用相同的转换函数或构造函数时,才可以比较用户定义的转换序列-此处不是这种情况。 Hence, this is ambiguous in C++11 (and in the C++14 CD). 因此,这在C ++ 11(和C ++ 14 CD)中是模棱两可的。

Note that here we have a subtle point to explore 请注意,在这里我们有一个细微的探索之处

struct X { operator int(); X(){/*nonaggregate*/} };

void f(X);
void f(int);

int main() {
  X x;
  f({x}); // ambiguity!
  f(x); // OK, calls first f
}

This counter intuitive result will probably be fixed in the same run with fixing the aggregate-initialization weirdness mentioned above (both will call the first f). 与上面提到的聚合初始化怪异度(同时都称为第一个f)相同的运行可能会解决此反直观结果。 This is implemented by saying that {x}->X becomes an identity conversion (as is X->x ). 这是通过说{x}->X成为身份转换( X->x )来实现的。 Currently, it is a user-defined conversion. 当前,这是用户定义的转换。

So, ambiguity here . 因此, 这里含糊不清

B2 b2b(x, {{P<Y>(y)}});

For the constructor with parameter const P<Y>& , we again split the arguments and get {P<Y> object} argument passed to the constructor(s) of P<Y> . 对于具有参数构造const P<Y>&中,我们再次分裂的参数和获得{P<Y> object}传递给的构造(多个)参数P<Y> Remember that P<Y> has a copy constructor. 请记住, P<Y>具有复制构造函数。 But the complication here is that we are not allowed to use it (see 13.3.3.1p4), because it would require a user defined conversion. 但是这里的复杂之处在于我们不允许使用它(请参阅13.3.3.1p4),因为它将需要用户定义的转换。 The only constructor left is the one taking Y , but an Y cannot be initialized by {P<Y> object} . 唯一的构造左侧是一个采取Y ,而是一个Y不能被初始化{P<Y> object}

For the constructor with parameter A<P<Y>> , the {{P<Y> object}} can initialize a std::initializer_list<P<Y>> , because {P<Y> object} is convertible to P<Y> (other than with Y above - dang, aggregates). 对于参数A<P<Y>>的构造函数, {{P<Y> object}}可以初始化std::initializer_list<P<Y>> ,因为{P<Y> object}可转换为P<Y> (不包括Y大于-dang的聚合)。

So, second constructor called successfully . 因此, 第二个构造函数成功调用


Summary for all 4 所有4的摘要

  • second constructor called successfully 第二个构造函数成功调用
  • under the assumption that std::initializer_list<T> is an aggregate, this is fine and call the second constructor successfully std::initializer_list<T>是一个聚合的假设下,这很好,可以成功调用第二个构造函数
  • ambiguity here 这里的歧义
  • second constructor called successfully 第二个构造函数成功调用

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

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