列挙型が特定の列挙子をもっているか判定する

ひさしぶりにテンプレートメタプログラミングをしました。

候補となる列挙型がだいたい同じ列挙子をもっているけど、一部の列挙型にだけある列挙子が存在する場合もある、という場合に処理を集約させたい場合のコードです。

#define DEFINE_HAS_ENUMERATOR(name) \
struct has_enumerator_##name { \
    template <class T> \
    static constexpr decltype(T::name, bool{}) \
        call(T) { return true; } \
    static constexpr bool \
        call(...) { return false; } \
}

#define HAS_ENUMERATOR(type, name) has_enumerator_##name ::call(type{})

DEFINE_HAS_ENUMERATOR(c);

#include <iostream>
#include <string>
#include <stdexcept>

template <class T>
std::string f(T x) {
    // 特定の列挙型にしかない列挙子
    if constexpr (HAS_ENUMERATOR(T, c)) {
        if (x == T::c) {
            return "c";
        }
    }

    // 共通
    switch (x) {
        case T::a:
            return "a";
        case T::b:
            return "b";
        default:
            throw std::invalid_argument("invalid enumerator");
    }
}

enum class A { a, b };
enum class B { a, b, c };

int main() {
    std::cout << f(A::a) << std::endl;
    std::cout << f(B::c) << std::endl;
}

出力:

a
c

if constexprはべんりですね。

マクロはなくせたらよかったのですが、関数ローカルで関数テンプレートをもつ関数オブジェクトが定義できなかったので泣く泣くこうなっています。std::variantでよく使われるoverload (こういうの) をうまく使えばできるかもしれませんが、今回はそこまではやってません。