Home > Software design >  encapsulate reference to templated function inside compile time object
encapsulate reference to templated function inside compile time object

Time:04-11

As of writing this metaclasses are sadly not a feature.

I am trying to encapsulate a reference to a templated function inside some compile time object, ideally the compile time object is easy to make, something like a type with a consteval ctor.

Doing this with a non templated function ptr is trivial:

template <typename T>
struct Magma { //magma trait

    T (& combine)(T, T);

    consteval Magma(decltype(combine) c): combine{c} {}
};

template <typename T, const Magma<T> & mag>
T add(T x, T y) {return mag.combine(x, y);}

Here is an example of a naive attempt to do so with a templated function ptr:

template <template<typename> typename F>
struct Functor { //functor trait

    template <typename A, typename B> //member template compilation error
    F<B> (& map)(std::function<B(A)>, F<A>);

    consteval Functor(decltype(map) m): map{m} {}
};

//...

template <template<typename>typename F, const Functor<F> & ftor>

This fails because on the member template case, sadly there is no way to tell the compiler that the type will only exist at compile time, not that (as of writing) it would care.

Issues I have faced:

  • I would use C 20 concepts do write temp_callable_concept auto map or something similar however auto is not a valid identifier for a data member.

  • I could probably move the type definition into the template of Functors using C 20 concepts however that goes against the very point of encapsulating the function.

  • I could write Functor as a concept and have "instances" of Functor be classes with visible static member functions with matching name and signature; but that makes the "instances" obtuse to implement.

Final thoughts

I do not see a way to do this right now and I believe that this requires C to allow explicitly compile time types with data member signatures that match those of template args.

If anyone knows a way to encapsulate this behaviour please leave an answer, or if you have any questions leave a comment.

CodePudding user response:

I could write Functor as a concept and have "instances" of Functor be classes with visible static member functions with matching name and signature; but that makes the "instances" obtuse to implement.

This is usually how it is done. It is a little unweidly although its not all that different from how traits are done in rust or typeclasses in haskell. If you wanted to implement haskell Functor for example:

template<template<typename> typename T>
struct Functor : public std::false_type {};

template<>
struct Functor<std::vector> : public std::true_type {
    template<typename T, typename F>
    requires (std::invocable<F, T>)
    static auto fmap(std::vector<T> v, F&& f) -> std::invoke_result_t<F, T> {
        // ...
    }
};

template<template<typename> typename C, typename T, typename F>
requires (Functor<C>::value && std::invocable<std::remove_cvref_t<F>, T>)
auto fmap(C<T> v, F&& f) {
    return Functor<C>::fmap(v, std::forward<F>(f));
}

This then lets you use it as simply fmap giving you the compile time polymorphism while still allowing the trait like implementation. Really you should also make a concept that specifies the requirements on Functor implementations and use that instead of just checking for any implementation (with value)

  • Related