以下の内容はhttps://pyopyopyo.hatenablog.com/entry/2025/05/12/183000より取得しました。


C++20の concepts を使ったSFINAE

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 has_func を実装します

    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++20のコードを見たあとにこれをみると,如何にC++11がクソで,C++20が素晴らしいかが理解できるかと思います

実装例(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はエラーとなります




以上の内容はhttps://pyopyopyo.hatenablog.com/entry/2025/05/12/183000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14