auto と decltype は、C++11 以降で導入された型導出を支援するキーワードです。どちらもコードの冗長さを減らすのに役立ちますが、導出の条件や挙動は明確に異なります。
auto の動作と注意点
auto を使うと、宣言時に初期化式から変数の型を自動的に決定できます。この導出はコンパイル時に実行され、実行時のオーバーヘッドはありません。
auto value = 42; // value は int として導出
auto precision = 3.14159; // precision は double として導出
auto label = "sample"; // label は const char* として導出
参照や修飾子を明示的に指定しない場合、auto はこれらの属性を破棄します。たとえば以下のコードを実行すると、出力は「11 11」になります。
#include <iostream>
using namespace std;
int main() {
int base = 10;
int& refBase = base;
auto copied = refBase; // refBase は int& だが、copied は int になる(参照は破棄)
refBase = 11;
cout << base << " "; // 11 を出力
copied = 12; // copied の変更は base に影響しない
cout << base << endl; // 11 を出力
}
一方、明示的に参照や const を指定すれば、その属性は保持されます。
auto& refCopy = base; // refCopy は int& 型(base を参照)
const auto constVal = base; // constVal は const int 型(変更不可)
decltype の型導出ルール
decltype は、式または変数の「型」を直接導出するための機能です。auto と異なり、式の值辺カテゴリ(左値/右値)や括弧の有無に敏感に反応し、uw coherent な型情報を保持します。
基本的な導出規則:
- 識別子やメンバーアクセス(括弧なし) → 対応する宣言時の型をそのまま返す。
- 関数呼び出しや演算子適用 → 関数の戻り値型を返す。
- 括弧付きの左値式
→
decltype((expr))は左値参照型(T&)を返す。 - 右値式
→
decltype(expr)は値型(T)を返す。
例:
int var;
const int&& rvalueRef();
struct Data { double rate; };
const Data* ptr = new Data();
decltype(var) x1; // x1 の型は int
decltype(rvalueRef()) x2 = 5; // x2 の型は const int&&
decltype(ptr->rate) x3; // x3 の型は double
decltype((ptr->rate)) x4; // x4 の型は const double&(左値因而)
// 括弧の違いに注意:((ptr->rate)) は式として評価され左値と見なされる
引用縮約(Reference Collapsing)
テンプレート展開時に decltype が導出した型が参照かどうか判定される際、C++ では引用縮約ルールが適用されます:
T& &、T& &&、T&& &→T&T&& &&→T&&
この性質は Perfect Forwarding を実現する std::forward や <utility> 関連の実装で重要です。
関数テンプレートでの応用例
型推論を活かして引数の加算結果の型を自動判別する関数テンプレートを定義すると、以下のように記述できます。
#include <utility>
#include <iostream>
#include <string>
using namespace std;
template <typename T1, typename T2>
auto combine(T1&& lhs, T2&& rhs)
-> decltype(forward<T1>(lhs) + forward<T2>(rhs)) {
return forward<T1>(lhs) + forward<T2>(rhs);
}
// ユーザ定義型
class Number {
int data_;
public:
Number(int v) : data_(v) {}
friend Number operator+(const Number& a, const Number& b) {
return Number(a.data_ + b.data_);
}
int value() const { return data_; }
};
int main() {
cout << combine(5, 7) << endl; // int + int → int
cout << combine(3.5f, 2.2) << endl; // float + double → double
cout << combine(string("Hello"), " World") << endl;
Number n1(10), n2(20);
auto sum = combine(n1, n2);
cout << sum.value() << endl;
}
このように decltype を戻り値型に用いると、引数の型に応じて最適な暗黙の変換後の型で戻り値を導出できます。
ただし注意点として、decltype 内で使われる式が依存しない(non-dependent)場合、テンプレート宣言段階で即時評価され、コンパイルエラーが早期に検出されます。たとえば以下のコードでは、std::declval の誤使用により early error が発生します:
template <typename T>
class CallableCheck {
template <typename U>
static decltype(std::declval<T>()()) test(int); // T が関数型でない場合コンパイルエラー
template <typename U>
static char test(...);
public:
static constexpr bool value = sizeof(test<T>(0)) == 1;
};
このように decltype は型検査と式評価の双方に深く関与する機能であり、auto とは使い分けが必要です。