LoginSignup
1
0

More than 1 year has passed since last update.

テンプレート関数の可変引数テンプレートをdecltypeを使って再帰処理できない

Last updated at Posted at 2020-08-22

コンパイルエラー: 戻り値型推論のdecltype内で再帰処理

複数の型を受け取り末尾の型を返す処理を考える。
試しに下のようなコードを書いてみたがコンパイルできなかった(テストコード@Wandbox)。

tail_failure1.hpp
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

成功: クラステンプレートによる再帰

結論としては、以下のようにクラステンプレートを用いることで実現できた。

tail_success.hpp
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...>::typetypenameを忘れないように。
忘れがちであるが、これを付けないと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。

参考


  1. パラメータパックは内包する引数が0個の場合も可 

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0