12

以下はコンパイルに失敗しますclang35 -std=c++11:

#include <iostream>
#include <string>
#include <initializer_list>

class A
{
 public:
  A(int, bool) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
  A(int, double) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
  A(std::initializer_list<int>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};

int main()
{
  A a1 = {1, 1.0};
  return 0;
}

エラーあり

init.cpp:15:14: error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
  A a1 = {1, 1.0};
             ^~~
init.cpp:15:14: note: insert an explicit cast to silence this issue
  A a1 = {1, 1.0};
             ^~~
             static_cast<int>( )

OTOH、縮小について警告し、コンパイルしますg++48 -std=c++11

init.cpp: In function ‘int main()’:
init.cpp:15:17: warning: narrowing conversion of ‘1.0e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
   A a1 = {1, 1.0};
                 ^
init.cpp:15:17: warning: narrowing conversion of ‘1.0e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]

結果を生成します

A::A(std::initializer_list<int>)

どちらの動作も理にかなっていますか? cppreferenceからの引用

std::initializer_list を唯一の引数として、または残りの引数にデフォルト値がある場合は最初の引数として取るすべてのコンストラクターが調べられ、型 std::initializer_list の単一の引数に対するオーバーロード解決によって照合されます。

前の段階で一致が生成されない場合、T のすべてのコンストラクターは、braced-init-list の要素で構成される引数のセットに対するオーバーロードの解決に参加します。ただし、非縮小変換のみが許可されるという制限があります。この段階で、copy-list-initialization に最適な明示的なコンストラクターが生成される場合、コンパイルは失敗します (単純な copy-initialization では、明示的なコンストラクターはまったく考慮されないことに注意してください)。

A(std::initializer_list<int>)縮小変換は許可されていないため、オーバーロード解決ステップがコンストラクターと一致せず、代わりにコンストラクターと一致すると予想されA(int, double)ます。たとえば、andと printの両方でコンパイルするように変更A(std::initializer_list<int>)すると、A(std::initializer_list<std::string>)clang35g++48

A::A(int, double)

予想通り。

4

1 に答える 1

11

動作は理にかなっています。Scott Meyers は、Effective Modern C++ で次のようにほぼ同じ例を示しています (原文で強調)。

ただし、1 つ以上のコンストラクターが type のパラメーターを宣言する場合std::initializer_list、波括弧付きの初期化構文を使用する呼び出しは、std;:initializer_lists を取るオーバーロードを強く優先します。強く。コンパイラが中括弧付き初期化子を使用した呼び出しを を受け取るコンストラクタであると解釈する方法があればstd::initializer_list、コンパイラはその解釈を採用します。

このクラスを使用した例:

class Widget {
public:
    Widget(int, bool);
    Widget(int, double);
    Widget(std::initializer_list<long double>);
};

Widget w1(10, true); // calls first ctor
Widget w2{10, true}; // calls std::initializer_list ctor
Widget w3(10, 5.0); // calls second ctor
Widget w4{10, 5.0}; // calls std::initializer_list ctor

これらの 2 つの呼び出しは、initializer_list両方の引数の変換を伴うにもかかわらず、また他のコンストラクターが完全に一致しているにもかかわらず、ctor を呼び出します。

さらに:

ブレース付き初期化子を s を取るコンストラクターと一致させるというコンパイラーの決定は非常に強力であり、最適なコンストラクターを呼び出すことができないstd::initializer_list場合でも優先されます。std::initializer_list例えば:

class Widget {
public:
    Widget(int, bool); // as before
    Widget(int, double); // as before
    Widget(std::initializer_list<bool> ); // now bool
};

Widget w{10, 5.0}; // error! requires narrowing conversions

どちらのコンパイラも、正しいオーバーロード ( initializer_list1 つ) を選択します。これは、標準 (§13.3.1.7) から要求されていることがわかります。

非集約型クラス タイプのオブジェクトTがリスト初期化されている場合 (8.5.4)、オーバーロードの解決では、次の 2 つのフェーズでコンストラクターが選択されます。

(1.1) — 最初は、候補関数はクラスの初期化子リスト コンストラクター (8.5.4) でTあり、引数リストは単一の引数としての初期化子リストで構成されます。
(1.2) — 実行可能な初期化子リスト コンストラクターが見つからない場合、オーバーロードの解決が再度実行されます。ここで、候補関数はクラスのすべてのコンストラクターでTあり、引数リストは初期化子リストの要素で構成されます。

ただし、その特定のコンストラクターを呼び出すには、絞り込みが必要です。8.5.1:

initializer-clauseが式であり、式を変換するために縮小変換 (8.5.4) が必要な場合、プログラムは不適切な形式です。

したがって、プログラムの形式が正しくありません。この場合、clang はエラーをスローすることを選択し、gcc は警告を発行することを選択します。両方のコンパイラが準拠しています。

于 2015-01-21T02:05:15.993 に答える