是否可以通过列表初始化调用用户定义的转换函数? [英] Is it possible to invoke a user-defined conversion function via list-initialization?

查看:263
本文介绍了是否可以通过列表初始化调用用户定义的转换函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此程式是否合法?

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

int main(){
X {Y {}}; // ??错误
}

n2672 ,并经 defect 978 13.3.3.1 [over.best.ics] has:


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


它的结果是使用列表初始化转换指定转换是非法的:

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

据我所知 n2640 ,列表初始化应该能够替换直接初始化和复制初始化的所有使用,似乎没有办法从类型 Y 仅使用list-initialization构造一个 X 类型的对象: / p>

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

这是标准的实际意图;

解决方案

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 code> S 是初始化表达式的类类型, T 是要初始化的对象的类型,因此没有纬度以便插入另一个用户定义的转换转换序列。 (13.3.1.4p1b1是另一回事;见下文)。



缺陷84修复了 auto_ptr code> auto_ptr< Derived> - > auto_ptr< Base> - > auto_ptr_ref< Base> - auto_ptr< Base> ,通过两个转换函数和转换构造函数)转换序列允许在类复制初始化的第二步中构造函数的单个参数(这里是 auto_ptr 的构造函数 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 然后添加:

$ b $当将初始化器列表作为单个参数传递时,或者当初始化器列表只有一个元素并且转换为某个类X或引用(可能)时,通过13.3.1.7 [over.match.list] [...]


这显然是困惑的,因为13.3.1.3和13.3.1.7作为候选者的唯一转换是构造函数,而不是转换函数。 缺陷978 更正了此问题:


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

这也使得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);错误由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}});

编辑:经过所有这些,我发现讨论 Johannes Schaub 确认此分析结果:


用于列表初始化的复制构造函数
因为我们允许使用嵌套的用户定义的转换,所以我们
总是可以产生一个不明确的第二个转换路径,首先调用
的复制构造函数,然后做我们为其他
的转换做了。







缺陷报告。我将建议拆分13.3.3.1p4:


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.conv]的

  • .match.ref]在所有情况下,只考虑标准转换序列和省略号转换序列,



<当将初始化器列表作为单个参数传递时,考虑由13.3.1.7 [over.match.list]作为候选者的类 X 的构造函数的第一个参数当初始化器列表只有一个元素时,用户定义的转换为 X 或引用(可能为 cv - 限定) X 仅在用户定义的转换由转换函数指定时才被考虑。 [注意:因为在列表初始化的上下文中,隐式转换序列允许多个用户定义的转换,所以这个限制是必要的,以确保 X ,使用不是类型 X 的单个参数 a X 派生的 X 的构造函数不会含糊不清。 / code>对象本身由 a 构造。 - end note ]



Is this program legal?

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

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

After n2672, and as amended by defect 978, 13.3.3.1 [over.best.ics] has:

4 - However, when considering the argument of a constructor or user-defined conversion function that is a candidate [...] by 13.3.1.7 [...] when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X [...], only standard conversion sequences and ellipsis conversion sequences are considered.

This seems rather perverse; it has the result that specifying a conversion using a list-initialization cast is illegal:

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

As I understand n2640, list-initialization is supposed to be able to replace all uses of direct-initialization and copy-initialization, but there seems no way to construct an object of type X from an object of type Y using only list-initialization:

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

Is this the actual intent of the standard; if not, how should it read or be read?

解决方案

The original intent of 13.3.3.1p4 is to describe how to apply the requirement in 12.3p4 that:

4 - At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.

Before defect 84, 13.3.3.1p4 was almost purely informative:

4 - In the context of an initialization by user-defined conversion (i.e., when considering the argument of a user-defined conversion function; see 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv]), only standard conversion sequences and ellipsis conversion sequences are allowed.

This is because 13.3.1.4 paragraph 1 bullet 2 and 13.3.1.5p1b1 restrict the candidate functions to those on class S yielding type T, where S is the class type of the initializer expression and T is the type of the object being initialized, so there is no latitude for another user-defined conversion conversion sequence to be inserted. (13.3.1.4p1b1 is another matter; see below).

Defect 84 repaired the auto_ptr loophole (i.e. auto_ptr<Derived> -> auto_ptr<Base> -> auto_ptr_ref<Base> -> auto_ptr<Base>, via two conversion functions and a converting constructor) by restricting the conversion sequences allowable for the single parameter of the constructor in the second step of class copy-initialization (here the constructor of auto_ptr<Base> taking auto_ptr_ref<Base>, disallowing the use of a conversion function to convert its argument from auto_ptr<Base>):

4 - However, when considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.

n2672 then adds:

[...] by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, [...]

This is clearly confused, as the only conversions that are a candidate by 13.3.1.3 and 13.3.1.7 are constructors, not conversion functions. Defect 978 corrects this:

4 - However, when considering the argument of a constructor or user-defined conversion function [...]

This also makes 13.3.1.4p1b1 consistent with 12.3p4, as it otherwise would allow unlimited application of converting constructors in copy-initialization:

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

The issue is then what the language referring to 13.3.1.7 means. X is being copy or move constructed so the language is excluding applying a user-defined conversion to arrive at its X argument. std::initializer_list has no conversion functions so the language must be intended to apply to something else; if it isn't intended to exclude conversion functions, it must exclude converting constructors:

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

There are two available constructors for the list-initialization; T::T(const T &) and T::T(S). By excluding the copy constructor from consideration (as its argument would need to be converted via a user-defined conversion sequence) we ensure that only the correct T::T(S) constructor is considered. In the absence of this language the list-initialization would be ambiguous. Passing the initializer list as a single argument works similarly:

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

Edit: and having gone through all that, I've found a discussion by Johannes Schaub that confirms this analysis:

This is intended to factor out the copy constructor for list initialization because since we are allowed to use nested user defined conversions, we could always produce an ambiguous second conversion path by first invoking the copy constructor and then doing the same as we did for the other conversions.


OK, off to submit a defect report. I'm going to propose splitting up 13.3.3.1p4:

4 - However, when considering the argument of a constructor or user-defined conversion function that is a candidate:

  • by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or
  • by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases,

only standard conversion sequences and ellipsis conversion sequences are considered; when considering the first argument of a constructor of a class X that is a candidate by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element, a user-defined conversion to X or reference to (possibly cv-qualified) X is only considered if its user-defined conversion is specified by a conversion function. [Note: because more than one user-defined conversion is allowed in an implicit conversion sequence in the context of list-initialization, this restriction is necessary to ensure that a converting constructor of X, called with a single argument a that is not of type X or a type derived from X, is not ambiguous against a constructor of X called with a temporary X object itself constructed from a. -- end note]

这篇关于是否可以通过列表初始化调用用户定义的转换函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆