C++20でやっとSFINAE(Substitution Failure Is Not An Error)が実用レベル(?)になったので,まとめておきます
C++20版のSFINAEだけでなく,C++17版やC++11以前版のSFINAEも用意しました.
見比べると如何にC++11がクソで,C++20が素晴らしいかが一目で理解できると思います.
お題: メンバ関数 func() の存在チェック
SFINAEはC++テンプレートメタプログラミングの(実質的)基礎です
ここではSFINAEの例として,class X について void X::func() が利用可能か否かの判定するテンプレートを作成します
具体的には
struct A { void func(); }; struct B { int func(); // func()は定義されているが,戻り値の型がvoidではなく intである点に注意 }; struct C { // そもそもfunc()が定義されてない };
に対して,以下の結果が得られるような template
static_assert(has_func<A>, "A has void func()"); // void A::func()があるので true static_assert(!has_func<B>, "B does not have void func()"); // funcは定義されているが,戻り値がintなのでfalse static_assert(!has_func<C>, "C does not have void func()"); // funcが未定義なので false
実装例(C++20版)
C++20だと concept を使い,簡潔に記述できます.
#include <concepts> #include <type_traits> template <typename T> concept has_func = requires(T t) { { t.func() } -> std::same_as<void>; };
C++20だとこれだけです
ソースコード全体は以下の通り
// C++20版の実装 #include <concepts> #include <type_traits> template <typename T> concept has_func = requires(T t) { { t.func() } -> std::same_as<void>; }; struct A { void func() {} }; struct B { int func() { return 42; } }; struct C { }; void main() { static_assert(has_func<A>, "A has void func()"); static_assert(!has_func<B>, "B does not have void func()"); static_assert(!has_func<C>, "C does not have func()"); }
これくらい簡潔に記述できるなら業務でも使えますね
以下,古いC++で実装した例wをみていきます
実装例(C++17版)
まずC++17版です
#include <type_traits> template <typename, typename = std::void_t<>> struct has_func : std::false_type {}; template<typename T> struct has_func<T, std::void_t<decltype(std::declval<T>().func())>> : std::is_same<decltype(std::declval<T>().func()), void> {};
当時はC++17で std::void_t が登場して,書きやすくなった!と喜んでいたのですが,C++20が使える今となっては,長く,解読しづらいコードです.
実装例(C++11版)
さらに時代を遡って C++11版です
#include <type_traits> template<typename...> struct make_void { typedef void type; }; template<typename... Ts> using void_t = typename make_void<Ts...>::type; template <typename, typename = std::void_t<>> struct has_func : std::false_type {}; template<typename T> struct has_func<T, void_t<decltype(std::declval<T>().func())>> : std::is_same<decltype(std::declval<T>().func()), void> {};
当時はstd:void_t が存在しない暗黒時代だったので,自前で void_t
またhas_funcを使うときは ::valueを参照する必要がありました.地味にめんどくさいです
static_assert(has_func<A>::value, "A has void func()"); static_assert(!has_func<B>::value, "B does not have void func()"); static_assert(!has_func<C>::value, "C does not have void func()");
// C++11版の実装 #include <type_traits> // std::void_t<>が利用できるのは C++17以降なので自前で void_t<T>を実装 template<typename...> struct make_void { typedef void type; }; template<typename... Ts> using void_t = typename make_void<Ts...>::type; template <typename, typename = std::void_t<>> struct has_func : std::false_type {}; template<typename T> struct has_func<T, void_t<decltype(std::declval<T>().func())>> : std::is_same<decltype(std::declval<T>().func()), void> {}; struct A { void func() {} }; struct B { int func() { return 42; } }; struct C { }; int main() { static_assert(has_func<A>::value, "A has void func()"); static_assert(!has_func<B>::value, "B does not have void func()"); static_assert(!has_func<C>::value, "C does not have func()"); }
実装例(C++11以前)
初期のSFINAEです.
template <typename T> class has_func { template <typename C> static char test( decltype(&C::func) ) ; template <typename C> static long test(...); public: enum { value = sizeof(test<T>(0)) == sizeof(char) }; };
こちらもhas_funcを使うときは ::valueを参照します.
static_assert(has_func<A>::value, "A has void func()"); static_assert(!has_func<B>::value, "B does not have void func()"); static_assert(!has_func<C>::value, "C does not have void func()");
SFINAEの概念を理解するには良いコードです
ただしこのコードにはバグがあり,void X::func()とint X::func()を区別できません.
たとえば int B::func() に対して
static_assert(!has_func<B>::value, "B does not have void func()");
この has_func<B>::value は falseとなるべきですが, バグにより値が true となり, static_assertはエラーとなります