yohhoyの日記

技術的メモをしていきたい日記

シーケンスコンテナ×イテレータ・ペア×リスト初期化×推論ガイド=?

C++1z(C++17)標準ライブラリのシーケンスコンテナ(std::vectorなど)にはクラステンプレート引数推論 推論ガイド(deduction guide) が追加されたが、イテレータ・ペアによるリスト初期化(list initialization)では意図しない型推論が行われる。かなり罠っぽい挙動。

// C++1z(C++17)
#include <vector>

std::vector v0{ 1, 2, 3, 4 };
// vector<int> に推論される
// コンストラクタvector(intializer_list<int>)を選択
// v0 = 4要素[1, 2, 3, 4]

std::vector v1( v0.begin(), v0.end() );
// 後述deduction guideによって
//   T = iterator_traits<vector<int>::iterator>::value_type
//   つまり vector<int> に推論される
// コンストラクタvector(InputIterator, InputIterator)を選択
// v1 = 4要素[1, 2, 3, 4]

std::vector v2{ v0.begin(), v0.end() };  // ★
// vector<vector<int>::iterator> に推論される
// コンストラクタvector(intializer_list<vector<int>::iterator>)を選択
// v2 = 2要素[v0.begin(), v0.end()]

std::vector<int> v3{ v0.begin(), v0.end() };
// (テンプレートパラメータ明示済みのため型推論は不要)
// コンストラクタvector(InputIterator, InputIterator)を選択
// v3 = 4要素[1, 2, 3, 4]

std::vector<T>クラステンプレートが提供する推論ガイド*1は次の通り。2個のイテレータ引数(InputIterator型)からコンテナ要素型Tを導く推論ガイドであり、std::deque, std::forward_list, std::listに対しても同じ目的の推論ガイドがそれぞれ提供される。

// <vector>ヘッダ
namespace std {
  template<class InputIterator>
    vector(InputIterator, InputIterator)
      -> vector<typename iterator_traits<InputIterator>::value_type>;
}

一方で、std::vector<T>をはじめとするシーケンスコンテナは、単一のstd::initializer_list<T>型をとるコンストラク*2を提供する。これによって変数v0のような統一初期化記法が可能となっているが、「std::initializer_list<T>コンストラクタは他コンストラクタよりも優先される」オーバーロード規則によって変数v2のような(プログラマの意図に反するであろう)型推論が行われる。*3

N4659(C++1z DIS) 16.3.1.7/p1, 16.3.1.8/p2より一部引用。

When objects of non-aggregate class type T are list-initialized such that 11.6.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors (11.6.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.

(snip)

Initialization and overload resolution are performed as described in 11.6 and 16.3.1.3, 16.3.1.4, or 16.3.1.7 (as appropriate for the type of initialization performed) for an object of a hypothetical class type, where the selected functions and function templates are considered to be the constructors of that class type for the purpose of forming an overload set, and the initializer is provided by the context in which class template argument deduction was performed. (snip)

関連URL

*1:本文中では簡単のためアロケータ Allocator を省略した。厳密な定義はN4659 26.3.11.1を参照のこと。

*2:正確には vector::vector(initializer_list<T>, const Allocator& = Allocator()) となっている。

*3:関連する話題としてP0702R1が提案・採択されている。vector v{vector{1, 2}}; と書いたとき、const vector<T>& をとるコピーコンストラクタよりも、 initializer_list<vector<T>> をとるコンストラクタが優先されてしまう問題への対処。