是否可以通过列表初始化来调用用户定义的转换函数?

时间:2012-10-01 16:59:36

标签: c++ c++11 implicit-conversion list-initialization

这个程序合法吗?

struct X { X(const X &); };
struct Y { operator X() const; };

int main() {
  X{Y{}};   // ?? error
}

n2672之后,经defect 978修改后, 13.3.3.1 [over.best.ics] 有:

  

4 - 然而,当初始化列表只有一个元素并转换为一个元素时,考虑构造函数或用户定义的转换函数的参数时,它是13.3.1.7 [...]的候选者。某些类X或对(可能是cv限定的)X的引用被认为是X [...]的构造函数的第一个参数,只考虑标准转换序列和省略号转换序列。

这似乎很不正常;它的结果是使用列表初始化强制转换指定转换是非法的:

void f(X);
f(Y{});     // OK
f(X{Y{}});  // ?? error

据我了解n2640,list-initialization应该能够替换直接初始化和复制初始化的所有用法,但是似乎没有办法构造类型为X的对象仅使用列表初始化的类型为Y的对象:

X x1(Y{});  // OK
X x2 = Y{}; // OK
X x3{Y{}};  // ?? error

这是标准的实际意图吗?如果没有,应该如何阅读或阅读?

2 个答案:

答案 0 :(得分:5)

13.3.3.1p4的初衷是描述如何在12.3p4中应用该要求:

  

4 - 最多只有一个用户定义的转换(构造函数或转换函数)隐式应用于单个值。

defect 84之前,13.3.3.1p4 几乎纯粹提供信息:

  

4 - 在用户定义的转换初始化的上下文中(即,在考虑用户定义的转换函数的参数时;参见13.3.1.4 [over.match.copy],13.3.1.5 [over.match] .conv]),只允许标准转换序列和省略号转换序列。

这是因为13.3.1.4第1段子弹2和13.3.1.5p1b1将候选函数限制为类S上产生类型T的函数,其中S是类的类型初始化表达式T是要初始化的对象的类型,因此没有其他用户定义的转换转换序列的插入范围。 (13.3.1.4p1b1是另一个问题;见下文)。

缺陷84修复了auto_ptr漏洞(即auto_ptr<Derived> -> auto_ptr<Base> -> auto_ptr_ref<Base> -> auto_ptr<Base>,通过两个转换函数和一个转换构造函数),通过在类复制的第二步中限制构造函数的单个参数允许的转换序列-initialization(这里auto_ptr<Base>的构造函数使用auto_ptr_ref<Base>,不允许使用转换函数将其参数从auto_ptr<Base>转换而来:

  

4 - 然而,当考虑用户定义的转换函数的参数时,作为13.3.1.3 [over.match.ctor]的候选者,当在类拷贝的第二步中复制临时文件时 - 初始化,或13.3.1.4 [over.match.copy],13.3.1.5 [over.match.conv]或13.3.1.6 [over.match.ref]在所有情况下,只有标准转换序列和省略号转换序列是允许的。

n2672然后添加:

  

[...]通过13.3.1.7 [over.match.list]将初始化列表作为单个参数传递,或者初始化列表只有一个元素并转换为某个类X或引用(可能是cv) -qualified)X被认为是X,[...]

的构造函数的第一个参数

这显然很困惑,因为13.3.1.3和13.3.1.7中唯一的候选转换是构造函数,而不是转换函数。 Defect 978更正了这一点:

  

4 - 但是,在考虑构造函数或用户定义的转换函数[...]

的参数时

这也使得13.3.1.4p1b1与12.3p4一致,否则将允许在复制初始化中无限制地应用转换构造函数:

struct S { S(int); };
struct T { T(S); };
void f(T);
f(0);   // copy-construct T by (convert int to S); error by 12.3p4

问题是13.3.1.7所指的语言是什么意思。 X正在复制或移动构造,因此语言不包括应用用户定义的转换来到达其X参数。 std::initializer_list没有转换函数,因此语言必须适用于其他内容;如果它不打算排除转换函数,则必须排除转换构造函数:

struct R {};
struct S { S(R); };
struct T { T(const T &); T(S); };
void f(T);
void g(R r) {
    f({r});
}

列表初始化有两个可用的构造函数; T::T(const T &)T::T(S)。通过排除复制构造函数(因为它的参数需要通过用户定义的转换序列进行转换),我们确保只考虑正确的T::T(S)构造函数。如果没有这种语言,列表初始化将是模糊的。将初始化列表作为单个参数传递的方式类似:

struct U { U(std::initializer_list<int>); };
struct V { V(const V &); V(U); };
void h(V);
h({{1, 2, 3}});

编辑:经过所有这些,我发现a discussion Johannes Schaub确认了这一分析:

  

这是为了将列表初始化的复制构造函数分解出来   因为我们被允许使用嵌套的用户定义转换,所以我们   通过首先调用,总是可以产生模糊的第二个转换路径   复制构造函数然后像我们对另一个那样做   转化


好的,关闭提交缺陷报告。我打算建议拆分13.3.3.1p4:

  

4 - 但是,在考虑构造函数或用户定义的转换函数的参数时,它是一个候选者:

     
      
  • by 13.3.1.3 [over.match.ctor]在类复制初始化的第二步中复制临时时调用,或者
  •   
  • 由13.3.1.4 [over.match.copy],13.3.1.5 [over.match.conv]或13.3.1.6 [over.match.ref]在所有情况下,
  •   
     

仅考虑标准转换序列和省略号转换序列;当将初始化列表作为单个参数传递或初始化列表只有一个元素时,在考虑13.3.1.7 [over.match.list]的候选类X的构造函数的第一个参数时,用户定义的转换为X或引用(可能 cv -qualified)X仅在转换函数指定其用户定义的转换时才被考虑。 [注意:因为在列表初始化的上下文中隐式转换序列中允许多个用户定义的转换,所以这个限制对于确保X的转换构造函数是必需的,使用不是a类型的单个参数X或从X派生的类型调用,对于使用临时{{1}调用的X的构造函数不明确对象本身是从X构造的。 - 结束记录]

答案 1 :(得分:3)

XCode 4.4附带的clang 3.1版本与您的解释一致,并拒绝X{Y{}};。和我一样,在重新阅读标准的相关部分几次后,FWIW。

如果我修改X的构造函数以获取两个参数,类型const X&,则clang接受语句Y y; X{y,y}。 (如果我尝试X{Y{},Y{}},它会崩溃...)。这似乎与13.3.3.1p4一致,后者要求仅针对单元素情况跳过用户定义的转换。

似乎最初仅在已经发生另一个用户定义转换的情况下添加对标准和省略号转换序列的限制。或者至少这是我阅读http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#84的方式。

有趣的是,标准是如何谨慎地将限制应用于复制初始化的第二步,复制初始化从已经具有正确类型的临时复制(并且可能通过用户获得 - 定义转换!)。然而,对于列表初始化,似乎不存在类似的机制...