コンパイルエラー: 戻り値型推論のdecltype内で再帰処理
複数の型を受け取り末尾の型を返す処理を考える。
試しに下のようなコードを書いてみたがコンパイルできなかった(テストコード@Wandbox)。
template<typename T>
T get_tail();
template<typename first, typename ...rest>
auto get_tail() -> decltype(get_tail<rest...>());
template<typename ...args>
using tail = decltype(get_tail<args...>());
tail<int> val1 = 0; // -> int val1
tail<int, char> val2 = 0; // -> char val2
tail<int, char, float> val3 = 0; // compile error
成功: クラステンプレートによる再帰
結論としては、以下のようにクラステンプレートを用いることで実現できた。
template<typename first, typename ...rest>
struct tail_impl
{
using type = typename tail_impl<rest...>::type;
};
template<typename T>
struct tail_impl<T>
{
using type = T;
};
template<typename ...args>
using tail = typename tail_impl<args...>::type;
注意点
template<typename T> struct tail_impl<T>
の<T>
は必要。
一つ目のtail_implの定義に対して、こちら(二つ目)の定義は特殊化である。
<T>
がなければ再定義(重複)となりコンパイルエラー。
特殊化がある場合はそちらが優先して採用されるため、引数が一つのときは二つ目の定義が採用される。
すなわち再帰が終了する。
using tail = typename tail_impl<args...>::type
のtypename
を忘れないように。
忘れがちであるが、これを付けないとtail_impl<args...>::type
が型名であるとコンパイラが解釈せずにエラーとなる。
応用例
当初実装したかったもの。
複数の型からサイズが最大となる型を選択。
複数型を格納するためのバッファのサイズを決定するために利用。
#include <type_traits>
template<typename first, typename ...rest>
struct max_size_type
{
//暫定最大サイズ型
using provisional_type = typename max_size_type<rest...>::type;
//最大サイズ型
using type = typename std::conditional<(sizeof(first) >= sizeof(provisional_type)), first, provisional_type>::type;
};
template<typename T>
struct max_size_type<T>
{
using type = T;
};
//使用例
max_size_type<int8_t, int64_t, int32_t, float, double>::type val;
関数テンプレートを用いた再帰で問題ない例
以下の例は関数テンプレートで問題ない。
関数の宣言が確定できるかが成否の分かれ目か。
//再帰終端
template<std::nullptr_t = nullptr>
constexpr size_t sum_of_size()
{
return 0;
};
template<typename first, typename ...rest>
constexpr size_t sum_of_size()
{
return sizeof(first) + sum_of_size<rest...>();
}
//使用例
sum_of_size<int8_t, int16_t, int32_t, int64_t>(); // -> 15
一つ目のsum_of_sizeのテンプレート引数template<std::nullptr_t = nullptr>
について、template<typename T>
やtemplate<typename T = void>
とするとコンパイルエラーとなる。
可変引数(二つ目の定義)のsum_of_sizeと一致するからである。1
呼び出される関数の候補が1つに絞れなければならない。
型名でなければよいのでtemplate<int = 0>
はOK。
参考
-
パラメータパックは内包する引数が0個の場合も可 ↩