Jun 24, 2022

[C++] Stateful Metaprogramming in C++20

Reference:
https://mc-deltat.github.io/articles/stateful-metaprogramming-cpp20

constexpr template function instantiation could be elided by compiler thus in order to assure template function being instantiated, use constexpr variadic variable instead; which is also a template but assure being instantiated.

Below code demonstrates even though the being generated at compile time the result can be different due to during the compile time state changes.


Example 1:
// declare of flag function, the function body doesn't exist
// unless template type setter being instantiated.
auto flag(int);

template<bool B> requires (!B)
struct setter {
	// 'flag' definition
    friend auto flag(int) {}

    static constexpr bool b = B;
};


// declare [[nodiscard]] and consteval make sure the
// template function won't be elided by compiler.
template<bool FlagVal>
[[nodiscard]]
consteval auto nonconstant_constant_impl() {
    if constexpr (FlagVal) {
        return true;
    }
    else {
    // 'setter' being instantiated.
        setter<FlagVal> s;
        return s.b;
    }
}


template<
    auto Arg = 0,
    bool FlagVal = requires { flag(Arg); },
    auto Val = nonconstant_constant_impl<FlagVal>()
>
constexpr auto nonconstant_constant = Val;

auto main() ->int{
    // a = 0
    // First evaluation in this TU; triggers 'setter' being
    // instantiated.
    constexpr bool a = nonconstant_constant<>;      

    // b = 1
    constexpr bool b = nonconstant_constant<>;

    // assertion passes.
    static_assert(a != b);
}

Example 2:
template<unsigned N>
struct reader {
    friend auto counted_flag(reader<N>);
};


template<unsigned N>
struct setter {
    friend auto counted_flag(reader<N>) {}

    static constexpr unsigned n = N;
};



template<
    auto Tag,
    unsigned NextVal = 0
>
[[nodiscard]]
consteval auto counter_impl() {
    constexpr bool counted_past_value = requires(reader<NextVal> r) {
        counted_flag(r);
    };

    if constexpr (counted_past_value) {
        return counter_impl<Tag, NextVal + 1>();
    }
    else {

        setter<NextVal> s;
        return s.n;
    }
}


template<
    auto Tag = []{}, // Each call generates different type.
    auto Val = counter_impl<Tag>()
>
constexpr auto counter = Val;



int main() {
    static_assert(counter<> == 0);
    static_assert(counter<> == 1);
    static_assert(counter<> == 2);
    static_assert(counter<> == 3);
    static_assert(counter<> == 4);
    static_assert(counter<> == 5);
    static_assert(counter<> == 6);
    static_assert(counter<> == 7);
    static_assert(counter<> == 8);
    static_assert(counter<> == 9);
    static_assert(counter<> == 10);
}

Example 3:
s.t I've played around back in 2013
#include <concepts>
#include <type_traits>


template<typename...>
struct type_list {};


template<class TypeList, typename T>
struct type_list_append;

template<typename... Ts, typename T>
struct type_list_append<type_list<Ts...>, T> {
    using type = type_list<Ts..., T>;
};


template<unsigned N, typename List>
struct state_t {
    static constexpr unsigned n = N;
    using list = List;
};


namespace {
    // used in reader; thus the 'state_func' shall be unique in each TU
    // thus not violating the ODR
    struct tu_tag {};
}


template<
    unsigned N,
    std::same_as<tu_tag> TUTag
>
struct reader {
    friend auto state_func(reader<N, TUTag>);
};


template<
    unsigned N,
    typename List,
    // Preventing accidentally passing random type thus violating ODR
    // It must be anonymous type 'tu_tag'
    std::same_as<tu_tag> TUTag
>
struct setter {
    // generated 'state_func' will be unique in each TU thanks to 'TUTag'
    friend auto state_func(reader<N, TUTag>) {
        return List{};
    }

    static constexpr state_t<N, List> state{};
};


template struct setter<0, type_list<>, tu_tag>;


template<
    // Preventing accidentally passing random type thus violating ODR
    // It must be anonymous type 'tu_tag'
    std::same_as<tu_tag> TUTag,
    auto EvalTag,
    unsigned N = 0
>
[[nodiscard]]
consteval auto get_state() {
    constexpr bool counted_past_n = requires(reader<N, TUTag> r) {
        state_func(r);
    };

    if constexpr (counted_past_n) {
        return get_state<TUTag, EvalTag, N + 1>();
    } else {
        constexpr reader<N - 1, TUTag> r;
        return state_t<N - 1, decltype(state_func(r))>{};
    }
}


template<
	// std::same_as is a concept; taking 2 arguments. <...> binds to back paramters while
	// assigned type is bound to forefront paramter.
    // Use to prevent accidentally passing random type thus violating ODR
    // It must be anonymous type 'tu_tag'
    std::same_as<tu_tag> TUTag = tu_tag,
    auto EvalTag = []{},
    auto State = get_state<TUTag, EvalTag>()
>
using get_list = typename std::remove_cvref_t<decltype(State)>::list;


template<
    typename T,
    // Preventing accidentally passing random type thus violating ODR
    // It must be anonymous type 'tu_tag'
    std::same_as<tu_tag> TUTag,
    auto EvalTag
>
[[nodiscard]]
consteval auto append_impl() {
    using cur_state = decltype(get_state<TUTag, EvalTag>());
    using cur_list = typename cur_state::list;
    using new_list = typename type_list_append<cur_list, T>::type;
    setter<cur_state::n + 1, new_list, TUTag> s;
    return s.state;
}


template<
    typename T,
    // Preventing accidentally passing random type thus violating ODR
    // It must be anonmous type 'tu_tag'
    std::same_as<tu_tag> TUTag = tu_tag,
    auto EvalTag = []{},
    auto State = append_impl<T, TUTag, EvalTag>()
>
constexpr auto append = [] { return State; };


int main() {
    static_assert(std::same_as<get_list<>, type_list<>>);

    append<int>();
    static_assert(std::same_as<get_list<>, type_list<int>>);

    append<float>();
    static_assert(std::same_as<get_list<>, type_list<int, float>>);

    append<char>();
    static_assert(std::same_as<get_list<>, type_list<int, float, char>>);
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.