2011年12月23日金曜日

【C++11】 list-initialization のオーバーロード解決が分からない

当初は、@cpp_akira さんの記事[C++] 波カッコ初期化のススメにかこつけて波カッコ初期化ができないケースについて書こうと思ったのですが、確認のために調べていたら何が正しいのか分からなくなりましたので、その内容を書いてみます。

#include <initializer_list>
struct A
{
 A(std::initializer_list<int>) {} // #1
 A(int n, double d) {}            // #2
};
A a{ 1, 2.5 }; // tries to call #1 but failed, or calls #2?

n3291 13.3.1.7 Initialization by list-initialization p1 には次のような記述があります。

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
  • Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

つまり initializer-list constructor だけを対象としてオーバーロード解決が行われ、viable function が見つからなかった場合に再度全コンストラクタに対してオーバーロード解決が行われます。リストが空の場合はデフォルトコンストラクタが呼ばれます。では先の例はどうなるんでしょうか。Overview of the New C++ (C++11)には次のような例があります。

class Widget {
public:
    Widget(double value, double uncertainty);           // #1
    Widget(std::initializer_list<std::string> values);  // #2
};
double d1, d2;
Widget w1 { d1, d2 }; // tries to call #2; fails because
                      // no double ⇒ string conversion

しかしこれに対しては stackoverflow での質問 C++0x: Overload Resolution規格の最新の変更が未反映との記述があります(Edit-2の所)。で、その質問にもある通り n3291 8.5.4p3 には次のような例があります。

struct S {
 S(std::initializer_list<double>); // #1
 S(const std::string&); // #2
// ...
};
const S& r1 = { 1, 2, 3.0 }; // OK: invoke #1
const S& r2 { "Spinach" }; // OK: invoke #2

これから考えると最初の例でも #2 を呼んで良い気はするのですが、しかしそれは narrowing conversion が #1 を viable にしないならば、です。これに対するコメントが http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110530/042586.html http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110530/042590.html に書かれています。オーバーロード解決で定数式であるかまたその値を確認するの?、あるいは相反してそうな関連する規格の記述について。うーん、どうなんでしょうか。ちなみに g++ 4.6/4.7 ではエラー、警告が出ます。

追記(2011/12/23)

上の ML (llvm の c front-end の commit メールみたいです) の同じスレッド内で解決していたみたいです。http://www.mail-archive.com/cfe-commits@cs.uiuc.edu/msg35510.html(こっちの方がスレッド見やすそうなのでアーカイブの場所を変えました) で URL が挙げられている n2640 Initializer Lists — Alternative Mechanism and Rationale (v. 2) の {}-lists and narrowing では

We propose that if — after deduction, overload resolution, etc. is done — a narrowing conversion (in the sense of N2531) occurs on a {}-enclosed value, the program is ill-formed. This is subtly different from N2531 in that it doesn't affect the existing overload resolution rules.

と記述されており、narrowing conversion の確認はオーバーロード解決の「後」となっています。ということで最初の例では narrowing ですが implicit な conversion があるため #1 が viable function として残り、全コンストラクタに対する再度のオーバーロード解決は行われません。結果として #1 がオーバーロード解決の結果として選択され、implicit narrowing を含むためエラー(ill-formed)となります。一方、Overview of the New C++ (C++11)、及び n3291 の例では implicit conversion が存在しないため initializer-list constructor が viable function とならず全コンストラクタに対する再度のオーバーロード解決が行われることになります。なお、ML の commit 対象のコードも修正されており、std::initializer_list を引数にとる initializer-list constructor を持つ class (template) A に対して

     // Narrowing conversions don't affect viability. The next two choose
     // the initializer_list constructor.
     { A<3> a{1, 1.0}; } // expected-error {{narrowing conversion}}
     { A<3> a = {1, 1.0}; } // expected-error {{narrowing conversion}}

と、いう風に記述されています。

0 件のコメント:

コメントを投稿