🐙
型情報を渡したいときの小技
この記事はC++ Advent Calendar 2024の6日目の記事です。(埋まってなかったので急遽書いた)
TL;DR
std::type_identity<T>
は状態を持たない(monostateな)ことを利用すると型情報だけを渡せる。
追記: std::in_place_type_t
を使う方が良さそう。
本文
hoge.h
#ifdef __cplusplus
extern "C" {
#endif
enum Type {
T1,
T2,
/* 略 */
};
typedef enum Type type_t;
int func(void* p, type_t type, /* たくさんの引数 */);
#ifdef __cplusplus
}
#endif
hoge.cpp
#include "hoge.h"
int func(void* p, type_t type, /* たくさんの引数 */) {
switch(type) {
case T1:
// T1のときの処理
return 0;
case T2:
// T2のときの処理
return 0;
/* 略 */
default:
return -1;
}
}
こんなコードがあったとします。hoge.cpp
はC++20を使えることとして、各型での処理は似ているとします。
まずはテンプレートで処理したくなりますね。
hoge.cpp
template<typename T>
int aux(T* p, /*たくさんの引数*/) {
// 処理
return 0;
}
int func(void* p, type_t type, /* たくさんの引数 */) {
switch(type) {
case T1:
return aux(reinterpret_cast<T1*>(p), /* たくさんの引数 */);
case T2:
return aux(reinterpret_cast<T2*>(p), /* たくさんの引数 */);
/* 略 */
default:
return -1;
}
}
たくさんの引数を毎回書くのは面倒ですね。マクロで解決する手もありますがそれはせずに、ラムダ式でキャプチャすることにします。
hoge.cpp
int func(void* p, type_t type, /* たくさんの引数 */) {
auto f = [&](auto dummy) {
// 処理
return 0;
};
switch(type) {
case T1:
return f(T1{});
case T2:
return f(T2{});
/* 略 */
default:
return -1;
}
}
大分スッキリしてきましたね。ただ、ダミー引数が気になります。上のコードではdummy
という変数名にしているので使わないことが明らかかもしれませんが、処理の部分でdummy
の値に依存した処理を書けてしまいます。また、T1
やT2
の値を構築するコストが大きいことも考えられます。
C++20だとジェネリックラムダのテンプレート構文が使えるので引数の代わりにこれを使って型情報を渡すことが出来ます。
hoge.cpp
int func(void* p, type_t type, /* たくさんの引数 */) {
auto f = [&]<typename T>() {
// 処理
return 0;
};
switch(type) {
case T1:
return f.template operator()<T1>();
case T2:
return f.template operator()<T2>();
/* 略 */
default:
return -1;
}
}
これで上で述べたダミー引数の問題は解消できます。このままでも良いですが、std::type_identity<T>
が状態を持たないことを用いて私は以下のようにしました。
追記: コメントで教えて頂いたstd::in_place_type_t
を使う方が良さそう。
hoge.cpp
#include <type_traits>
int func(void* p, type_t type, /* たくさんの引数 */) {
auto f = [&]<typename T>(std::type_identity<T>) {
// 処理
return 0;
};
switch(type) {
case T1:
return f(std::type_identity<T1>{});
case T2:
return f(std::type_identity<T2>{});
/* 略 */
default:
return -1;
}
}
おわりに
型推論の無効化のためのstd::type_identity<T>
を悪用(?)するとこんなことも出来るのはちょっと面白いですね。
ちなみにこの方法を思いついたのは以下のツイートを見たからというのもあります。
Discussion
そのような用途のために
std::in_place_type_t
があります。ありがとうございます。確かにこれを使うのが良さそうですね。