aboutsummaryrefslogblamecommitdiffhomepage
path: root/src/development/c++/meta4.cc
blob: 9c3be779a54e81cf89a319f0fba56302c2195f9a (plain) (tree)







































































































                                                                                                  
// Copyright (C) 2022 johannst

#include <cstdio>
#include <functional>
#include <map>
#include <string>
#include <type_traits>

template<typename R, typename... P>
struct registry {
    using FUNC = R (*)(P...);
    using SELF = registry<R, P...>;
    using RET = R;

    static SELF& get() {
        static SELF r;
        return r;
    }

    bool add(std::string nm, FUNC fn) {
        const auto r = m_fns.insert({std::move(nm), std::move(fn)});
        return r.second;
    }

    R invoke(const std::string& nm, P... p) const { return invoke_impl<R>(nm, p...); }

    void dump() const {
        for (const auto& it : m_fns) {
            std::puts(it.first.c_str());
        }
    }

  private:
    std::map<std::string, FUNC> m_fns;

    template<typename RET>
    std::enable_if_t<std::is_same_v<RET, void>> invoke_impl(const std::string& nm, P... p) const {
        const auto it = m_fns.find(nm);
        if (it == m_fns.end()) {
            return;
        }
        std::invoke(it->second, p...);
    }

    template<typename RET>
    std::enable_if_t<!std::is_same_v<RET, void>, RET> invoke_impl(const std::string& nm,
                                                                  P... p) const {
        const auto it = m_fns.find(nm);
        if (it == m_fns.end()) {
            static_assert(std::is_default_constructible_v<RET>,
                          "RET must be default constructible");
            return {};
        }
        return std::invoke(it->second, p...);
    }
};

#define TEST_REGISTER(REGISTRY, NAME)                                                      \
    static bool regfn_##REGISTRY##NAME() {                                                 \
        const bool r = REGISTRY::get().add(#NAME, NAME);                                   \
        if (!r) {                                                                          \
            std::puts("Failed to register test " #NAME ", same name already registered!"); \
            std::abort();                                                                  \
        }                                                                                  \
        return r;                                                                          \
    }                                                                                      \
    static const bool reg_##REGISTRY##NAME = regfn_##REGISTRY##NAME();

#define TEST(REGISTRY, NAME, ...)    \
    REGISTRY::RET NAME(__VA_ARGS__); \
    TEST_REGISTER(REGISTRY, NAME);   \
    REGISTRY::RET NAME(__VA_ARGS__)

// -- Usage 1 simple usage.

using REG1 = registry<void>;
TEST(REG1, test1) {
    std::puts("REG1::test1");
}
TEST(REG1, test2) {
    std::puts("REG1::test2");
}

// -- Usage 2 with convenience macro wrapper.

using REG2 = registry<void, bool>;
#define TEST2(NAME, ...) TEST(REG2, NAME, ##__VA_ARGS__)

TEST2(test1, bool val) {
    printf("REG2::test1 val %d\n", val);
}

int main() {
    const auto& R1 = REG1::get();
    R1.dump();
    R1.invoke("test1");
    R1.invoke("test2");

    const auto& R2 = REG2::get();
    R2.dump();
    R2.invoke("test1", true);

    return 0;
}