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 howeverauto
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" ofFunctor
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
)