テンプレート引数の自動推論
C++17以前、クラステンプレートのコンストラクタは関数テンプレートのようにテンプレート引数を自動で推論できませんでした。例えば、以下のようなコードは書けませんでした。
std::pair a{1, "a"s}; // C++17以降
代わりに、以下のように明示的に型を指定する必要がありました。
std::pair<int, string> a{1, "a"s}; // C++17以前
この欠点を補うため、標準ライブラリにはstd::make_pairのような関数が提供されており、関数テンプレートのテンプレート引数自動推論機能を利用して、コンストラクタ呼び出し時に型を明示する必要がなくなっていました。
auto a = std::make_pair(1, "a"s); // C++17以前
// これは以下と同等
// std::pair<int, string> a = std::make_pair<int, string>(1, string("a"));
// ここでは、std::make_pairに渡された引数の型から、コンパイラが自動でテンプレート引数を推論しています。
この解決策は理想的とは言えませんでした。理由は以下の通りです:
- make_pair, make_tupleのようなクラステンプレートを構築するための慣用構文を覚える必要がありました。
- make_sharedのような関数は、クラステンプレートのコンストラクタと機能的に等価ではない場合がありました。
C++17では、この問題が解決され、クラステンプレートのコンストラクタでもテンプレート引数の自動推論が可能になりました。
std::pair a{1, "a"s}; // C++17
// これは以下と同等
// std::pair<int, string> a{1, "a"s};
// 関数テンプレートと同様に、ここではコンパイラがstd::pairコンストラクタの引数型から自動でテンプレート引数を推論しています。
これにより、std::make_pairのような補助関数を必要としなくなりました。
実装例
#include <iostream>
#include <vector>
#include <functional>
#include <string>
#include <map>
#include <algorithm>
using namespace std;
int main()
{
vector data = {10, 20, 30}; // C++17
// vector<int> data = {10, 20, 30}; // C++17以前
function processor = [](int value){return value + 1;}; // C++17
// function<int(int)> processor = [](int value){return value + 1;}; // C++17以前
tuple record{100, 200, 500, "test"s}; // C++17
// tuple<int, double, string> record{100, 200, 500, "test"s}; // C++17以前
// auto record = make_tuple(100, 200, 500, "test"s); // C++17以前
sort(data.begin(), data.end(), greater{}); // C++17
// sort(data.begin(), data.end(), greater<>{}); // C++17以前
// sort(data.begin(), data.end(), greater<int>{}); // C++11以前
// dictionary d = {{1, "first"s}, {2, "second"s}}; // {1, "first"s} という中括弧初期化リストには型情報がない
// そのため、コンパイラはmapクラステンプレートの引数型を自動推論できません
dictionary d = {entry{1, "first"s}, {2, "second"s}}; // C++17
// dictionary<int, string> d = {{1, "first"s}, {2, "second"s}}; // C++17以前
}
カスタムクラステンプレートでの応用
template<typename DataType>
struct Wrapper
{
Wrapper(DataType* pointer) {} // コンストラクタ 1
Wrapper(DataType& value) {} // コンストラクタ 2
Wrapper(DataType const& value) {} // コンストラクタ 3
template<typename DeleterType>
Wrapper(DataType* pointer, DeleterType& deleter) {} // コンストラクタ 4
};
struct CustomDeleter {};
int main()
{
Wrapper w{(int*)nullptr}; // コンストラクタ 1が呼び出される
int x; Wrapper w2{x}; // コンストラクタ 2が呼び出される
Wrapper w3{0}; // コンストラクタ 3が呼び出される
CustomDeleter d;
Wrapper w4{(int*)nullptr, d}; // コンストラクタ 4が呼び出される
// 上記すべての場合、コンパイラは自動推論の結果Wrapper<int>と判断します
}
自動推論ガイド
場合によっては、コンパイラがクラステンプレートの引数を自動推論できないことがあります。例えば、テンプレート引数がネストされた型の場合です。このような場合、自動推論ガイドを追加してコンパイラを助ける必要があります。自動推論ガイドの形式は以下の通りです:
クラステンプレート名(引数リスト) -> クラステンプレートid
template<typename T>
struct TypeHelper { using type = T; };
template<typename T>
struct Wrapper
{
// ネストされた型を引数とする場合、自動推論ができない
Wrapper(typename TypeHelper<T>::type value) {}
};
// 自動推論ガイド
template<typename T>
Wrapper(T) -> Wrapper<T>;
int main()
{
Wrapper w(0); // コンパイラは自動推論の結果Wrapper<int>と判断します
}