5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++ TMP (テンプレートメタプログラミング) シリーズ

Part1 constexpr Part2 Concepts Part3 Variadic Part4 型リスト Part5 正規表現
👈 Now

はじめに

C++11で導入されたバリアディックテンプレート(可変長テンプレート)

これのおかげで:

  • std::tuple
  • std::variant
  • std::make_unique
  • 型安全なprintf

などが実現できるようになった。

今回は、この強力な機能を深掘りする。

基本構文

// ... が「パラメータパック」を表す
template<typename... Types>  // 型パラメータパック
void func(Types... args);    // 関数パラメータパック
// 展開例
func(1, 2.0, "hello");

// コンパイラが展開:
// func<int, double, const char*>(int, double, const char*)

パラメータパックの展開

基本パターン

template<typename... Args>
void print(Args... args) {
    // パックを展開
    ((std::cout << args << " "), ...);  // 折りたたみ式(C++17)
    std::cout << "\n";
}

print(1, 2.0, "hello");  // 1 2 hello

展開の場所

template<typename... Args>
void examples(Args... args) {
    // 1. 関数呼び出し
    other_func(args...);
    
    // 2. 初期化リスト
    int arr[] = {args...};
    
    // 3. テンプレート引数
    std::tuple<Args...> t;
    
    // 4. sizeof...
    constexpr size_t n = sizeof...(Args);  // パックの要素数
}

C++17 折りたたみ式

パックの全要素に演算子を適用する。

4つの形式

形式 展開結果
(E op ...) E1 op (E2 op (E3 op ...))
(... op E) ((E1 op E2) op E3) op ...
(E op ... op init) E1 op (E2 op (... op init))
(init op ... op E) ((init op E1) op E2) op ...
// 合計
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 右折りたたみ
}

// 初期値付き
template<typename... Args>
auto sum_init(Args... args) {
    return (args + ... + 0);  // 初期値0
}

// 論理AND
template<typename... Args>
bool all_true(Args... args) {
    return (args && ...);
}

// 論理OR
template<typename... Args>
bool any_true(Args... args) {
    return (args || ...);
}

カンマ演算子による副作用

template<typename... Args>
void print_all(Args... args) {
    ((std::cout << args << "\n"), ...);
}

// 展開: 
// (std::cout << arg1 << "\n"),
// ((std::cout << arg2 << "\n"),
//   ((std::cout << arg3 << "\n"), ...))

再帰的パック展開

C++11/14スタイル(折りたたみ式がない場合)。

// ベースケース
void print() {
    std::cout << "\n";
}

// 再帰ケース
template<typename First, typename... Rest>
void print(First first, Rest... rest) {
    std::cout << first << " ";
    print(rest...);  // 残りで再帰
}

print(1, 2.0, "hello");
// print(1, 2.0, "hello")
// -> cout << 1; print(2.0, "hello")
// -> cout << 2.0; print("hello")
// -> cout << "hello"; print()
// -> "\n"

インデックスシーケンス

コンパイル時のインデックス生成。

// std::index_sequence の使い方
template<typename Tuple, size_t... Is>
void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>) {
    ((std::cout << std::get<Is>(t) << " "), ...);
}

template<typename... Args>
void print_tuple(const std::tuple<Args...>& t) {
    print_tuple_impl(t, std::index_sequence_for<Args...>{});
}

auto t = std::make_tuple(1, 2.0, "hello");
print_tuple(t);  // 1 2 hello

make_index_sequence

// 0, 1, 2, ..., N-1 のシーケンスを生成
std::make_index_sequence<5>  // -> index_sequence<0, 1, 2, 3, 4>

// 型パラメータパックに対応するインデックス
template<typename... Args>
using indices_for = std::index_sequence_for<Args...>;

完全転送

template<typename... Args>
auto make_unique_wrapper(Args&&... args) {
    // 完全転送: 左辺値/右辺値を維持
    return std::make_unique<Widget>(std::forward<Args>(args)...);
}

// 各引数が個別にforward される
// make_unique<Widget>(forward<Args1>(arg1), forward<Args2>(arg2), ...)

実践例1: 型安全printf

template<typename T>
void format_arg(std::ostream& os, const char* fmt, size_t& pos, T&& arg) {
    while (fmt[pos] && fmt[pos] != '%') {
        os << fmt[pos++];
    }
    if (fmt[pos] == '%') {
        pos++;  // '%' をスキップ
        // フォーマット指定子を処理(簡略化)
        os << std::forward<T>(arg);
    }
}

template<typename... Args>
std::string safe_printf(const char* fmt, Args&&... args) {
    std::ostringstream os;
    size_t pos = 0;
    
    (format_arg(os, fmt, pos, std::forward<Args>(args)), ...);
    
    // 残りの文字を出力
    while (fmt[pos]) {
        os << fmt[pos++];
    }
    
    return os.str();
}

auto s = safe_printf("Hello %, you have % messages!", "Alice", 5);
// "Hello Alice, you have 5 messages!"

実践例2: 関数合成

template<typename... Funcs>
auto compose(Funcs... funcs) {
    return [=](auto x) {
        // 右から左に適用
        return (funcs(...), x);  // これでは動かない
    };
}

// 正しい実装
template<typename F>
auto compose(F f) {
    return f;
}

template<typename F, typename... Rest>
auto compose(F f, Rest... rest) {
    return [=](auto x) {
        return f(compose(rest...)(x));
    };
}

auto add1 = [](int x) { return x + 1; };
auto mul2 = [](int x) { return x * 2; };
auto sub3 = [](int x) { return x - 3; };

auto combined = compose(add1, mul2, sub3);
combined(10);  // add1(mul2(sub3(10))) = add1(mul2(7)) = add1(14) = 15

実践例3: ビジター

// 複数のラムダを継承してオーバーロードセットを作る
template<typename... Funcs>
struct overloaded : Funcs... {
    using Funcs::operator()...;  // usingでパック展開
};

// 推論ガイド
template<typename... Funcs>
overloaded(Funcs...) -> overloaded<Funcs...>;

// 使用例
std::variant<int, double, std::string> v = 42;

std::visit(overloaded{
    [](int i) { std::cout << "int: " << i << "\n"; },
    [](double d) { std::cout << "double: " << d << "\n"; },
    [](const std::string& s) { std::cout << "string: " << s << "\n"; }
}, v);

実践例4: タプル操作

// タプルの各要素に関数を適用
template<typename Tuple, typename F, size_t... Is>
auto tuple_transform_impl(Tuple&& t, F&& f, std::index_sequence<Is...>) {
    return std::make_tuple(f(std::get<Is>(std::forward<Tuple>(t)))...);
}

template<typename Tuple, typename F>
auto tuple_transform(Tuple&& t, F&& f) {
    constexpr size_t size = std::tuple_size_v<std::remove_reference_t<Tuple>>;
    return tuple_transform_impl(
        std::forward<Tuple>(t), 
        std::forward<F>(f),
        std::make_index_sequence<size>{}
    );
}

auto t = std::make_tuple(1, 2, 3);
auto doubled = tuple_transform(t, [](auto x) { return x * 2; });
// doubled = (2, 4, 6)

実践例5: 型安全なイベントシステム

template<typename... EventTypes>
class EventDispatcher {
    std::tuple<std::vector<std::function<void(EventTypes)>>...> handlers_;

public:
    template<typename Event>
    void subscribe(std::function<void(Event)> handler) {
        std::get<std::vector<std::function<void(Event)>>>(handlers_)
            .push_back(std::move(handler));
    }
    
    template<typename Event>
    void dispatch(Event&& event) {
        for (auto& handler : std::get<std::vector<std::function<void(Event)>>>(handlers_)) {
            handler(std::forward<Event>(event));
        }
    }
};

// 使用例
struct MouseClick { int x, y; };
struct KeyPress { char key; };

EventDispatcher<MouseClick, KeyPress> dispatcher;

dispatcher.subscribe<MouseClick>([](MouseClick e) {
    std::cout << "Click at (" << e.x << ", " << e.y << ")\n";
});

dispatcher.dispatch(MouseClick{100, 200});

コンパイル時文字列処理

template<char... Chars>
struct static_string {
    static constexpr char value[] = {Chars..., '\0'};
    static constexpr size_t size = sizeof...(Chars);
};

// ユーザー定義リテラル
template<typename Char, Char... Chars>
constexpr auto operator""_s() {
    return static_string<Chars...>{};
}

auto str = "hello"_s;
static_assert(str.size == 5);

パック展開のパターン集

template<typename... Args>
void patterns(Args... args) {
    // パターン1: 各要素に関数適用
    (func(args), ...);
    
    // パターン2: 各要素を式で加工
    (process(args * 2), ...);
    
    // パターン3: 条件付き
    ((condition(args) ? action(args) : void()), ...);
    
    // パターン4: 型変換
    (static_cast<int>(args), ...);
}

template<typename... Types>
struct TypePatterns {
    // パターン: 各型のvectorを持つタプル
    std::tuple<std::vector<Types>...> data;
    
    // パターン: 各型のポインタ
    std::tuple<Types*...> ptrs;
    
    // パターン: 各型をoptionalに
    std::tuple<std::optional<Types>...> optionals;
};

sizeof...の活用

template<typename... Args>
constexpr size_t count = sizeof...(Args);

template<typename... Args>
void debug_pack(Args... args) {
    std::cout << "Pack contains " << sizeof...(Args) << " types\n";
    std::cout << "Pack contains " << sizeof...(args) << " values\n";
}

// 空パックのチェック
template<typename... Args>
void maybe_empty(Args... args) {
    if constexpr (sizeof...(Args) == 0) {
        std::cout << "Empty!\n";
    } else {
        // ...
    }
}

まとめ

機能 用途
typename... Ts 型パラメータパック
Ts... args 関数パラメータパック
args... パック展開
sizeof...(Ts) パックのサイズ
(args op ...) 折りたたみ式
index_sequence インデックス生成

バリアディックテンプレートはジェネリックプログラミングの要。これがないとtuplevariantも作れない。

次回は型リストを使ったコンパイル時計算を解説する。

続きが気になったら、いいね・ストックしてもらえると嬉しいです!

5
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
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?