Edited at

4つのauto、4つのC++規格

プログラミング言語C++のキーワード auto による型推論のおかげで、C++プログラマはソースコード上で型(type)を明示しないプログラムを書けるようになりました。

2020年にリリース予定されている最新仕様「C++20」では、とうとう下記コードのようにautoまみれの関数テンプレート1を書けるようになります。ナニコレ...


cpp20.cpp

template <auto N>

auto func(auto x)
{
auto r = N + x;
return r;
}

// 呼出し例
assert( func<1>(0.5) == 1.5 );


この記事では、C++言語仕様改定により導入された各autoキーワードの役割を説明します。同時に、該当用法が存在しなかった時代でもコンパイル可能なソースコードへの書換えも行います。


C++20時代

C++20では 関数パラメータ型にautoを書ける ようになります。これはC++言語仕様へのコンセプト(Concept)導入とともに追加された、関数テンプレート定義の短縮記法 です。


cpp20.cpp(再掲)

template <auto N>

auto func(/*👉*/auto/*👈*/ x)
{
auto r = N + x;
return r;
}

現行C++17仕様の範囲内で書き直す場合、関数テンプレートに新たに型パラメータUを導入し、引数型として利用すればOKです。


cpp17.cpp

template <auto N, typename U>

auto func(U x)
{
auto r = N + x;
return r;
}

これならもう読めますよね?(無理?)


C++17時代

C++17では テンプレートパラメータ型にautoを書ける ようになりました。つまり 非型テンプレートパラメータのauto宣言 です。


cpp17.cpp(再掲)

template </*👉*/auto/*👈*/ N, typename U>

auto func(U x)
{
auto r = N + x;
return r;
}

C++14仕様で書き直す場合、関数テンプレートに新たな型パラメータTを導入し、そのうえで非型テンプレート引数Nを取らねばなりません。書き直した関数テンプレートにはTNの両パラメータを指定する必要があるため、呼出し側コードの記述も冗長になります。


cpp14.cpp

template <typename T, T N, typename U>

auto func(U x)
{
auto r = N + x;
return r;
}

// 呼出し例
assert( func<decltype(1), 1>(0.5) == 1.5 );


冗長。ダサい。しかし気を落とさずに時代逆行を続けましょう。


C++14時代

C++14では 関数戻り値型にautoとだけ書いておける ようになりました。関数本体にあるreturn文の内容から戻り値型が推論される、通常関数の戻り値型推論 という機能です。


cpp14.cpp(再掲)

template <typename T, T N, typename U>

/*👉*/auto/*👈*/ func(U x)
{
auto r = N + x;
return r;
}

C++11仕様で書き直す場合、戻り値型のautoキーワードはそのまま使えますが、関数宣言末尾への型明示が求められます。つまり関数本体のreturn文で返す予定の型を、宣言時にも型導出しておく必要があるのです。


cpp11.cpp

template <typename T, T N, typename U>

auto func(U x) -> decltype(N + x)
{
auto r = N + x;
return r;
}

// 呼出し例
assert( func<decltype(1), 1>(0.5) == 1.5 );


なにこれ面倒くさい。もうちょっとだけ続くんじゃ


C++11時代

C++11は キーワードautoが型推論の意味を持つ ようになった最初のバージョンです。厳密には 変数の型推論戻り値の型を後置する関数宣言構文autoキーワードは異なる意味を持ちますが、さほど気にしなくても良いでしょう(前者=型推論/後者=プレースホルダ)。


cpp11.cpp(再掲)

template <typename T, T N, typename U>

auto func(U x) -> decltype(N + x)
{
/*👉*/auto/*👈*/ r = N + x;
return r;
}

// 呼出し例
assert( func<decltype(1), 1>(0.5) == 1.5 );


さて、これをC++03仕様で書き直すのは本当に大仕事です。C++03時代のautoキーワードは実効上の意味をもたず2式から型を導出するdecltype も存在しません \(^o^)/


C++03暗黒時代

暗黒のC++03時代には、偉大なる Boostライブラリ が大抵の問題を解決してくれました(ほんまか?)。2019年にもなって古のテクニックを紹介する意味もないでしょうから、意図通り動いたソースコードだけ貼っておひらきとします。


cpp03_with_boost.cpp

#include <boost/typeof/typeof.hpp>

#include <boost/utility/declval.hpp>

template <typename T, typename U>
struct result_of_add {
typedef BOOST_TYPEOF_TPL(
boost::declval<T>() + boost::declval<U>()
) type;
};

template <typename T, T N, typename U>
typename result_of_add<T, U>::type
func(U x) {
BOOST_AUTO(r, N + x);
return r;
}

// 呼出し例
assert(( func<BOOST_TYPEOF(1), 1>(0.5) == 1.5 ));






  1. autoキーワードの役割説明のためのコードですから、関数テンプレートの具体的な処理内容にはあまり意味がありません。 



  2. C++03時代のキーワードautoは、ローカル変数が「自動記憶域期間を持つ」という意味でした。つまり何気なく記述するローカル変数宣言 int a;auto int a; と等しいという意味です。当然ながらこの目的でautoを明示記述するC++プログラマなど存在せず、C++11からは新たに「型推論」の意味を与えられて復活しました。