I have wrote a gerneric type Meta
:
template <size_t N>
struct Meta
{
// ...
};
And I need to make some MetaModifier
(s) to build it in compile time.
MetaModifier(s) seem like below:
struct FooModifier
{
template <size_t N, size_t N2>
Meta<N2> apply(Meta<N> meta)
{
// ...
}
};
Some of Modifiers' apply
will change Meta<N>
to Meta<N2>
.
Use case:
constexpr Meta<1> default_meta{};
// Call variadic function
constexpr auto meta = apply_modifiers(default_meta, FooModifier{}, BarModifier{});
This is my implementation:
template <typename Modifier, size_t N>
static constexpr auto apply_modifier(Meta<N> meta, Modifier M)
{
return M.apply(meta);
}
template <size_t N, typename FirstModifier, typename... RestModifiers>
static constexpr auto apply_modifiers(Meta<N> meta, FirstModifier F, RestModifiers... rest)
{
return apply_modifiers(apply_modifier(meta, F), rest...);
}
template <size_t N>
static constexpr auto apply_modifiers(Meta<N> meta)
{
return meta;
}
Currently it works, but I want to check types of Modifier(s) to make sure it has a method Meta<N2> apply(Meta<N>)
, report a friendly message if it is not matched. I tried to make a concept MetaModifier
but failed. How can I write a correct concept or use some compile time checking to ensure it?
UPDATE: Is there a way to try two steps like below (pseudo code)?
template <typename Modifier, size_t N>
static constexpr auto apply_modifier(Meta<N> meta, Modifier M)
{
// First step: test it has a method apply
if constexpr (has_apply<Modifier, Meta<N>>)
{
// Second step: test the return type of apply
auto r = M.apply(meta);
if constexpr (is_meta_type<decltype(r)>)
{
return r;
}
else
{
static_assert(false, "The return type of apply is not Meta<N>");
}
}
else
{
static_assert(false, "Modifier does not have apply method");
}
}
A concept is there to check if a type satisfies some property. It can't check if a template satisfies some property, because a template isn't a type.
apply_modifier
needs to know which N2
to use when it is passed a FooModifier
, the easiest way is to make N2
a template argument of FooModifier
.
template <size_t N2>
struct FooModifier
{
template <size_t N>
Meta<N2> apply(Meta<N> meta)
{
// ...
}
};
Another possibility is that N2
is dependant on N
, e.g.
struct BarModifier
{
template <size_t N>
Meta<N + 1> apply(Meta<N> meta)
{
// ...
}
};
Having done that, we can construct constraints for apply_modifier
and apply_modifiers
template <typename T>
struct is_meta : std::false_type {};
template <size_t N>
struct is_meta<Meta<N>> : std::true_type {};
template <typename T>
concept meta = is_meta<T>::value;
template <typename Mod, typename M>
concept meta_modifier = meta<M> && requires(Mod mod, M m)
{
{ mod.apply(m) } -> meta;
}
Our meta_modifier
concept requires knowing the Meta
that it will modify, which means that you can still check modifiers that only apply to certain Meta
specialisations
template <meta M, meta_modifier<M> Modifier>
static constexpr auto apply_modifier(M meta, Modifier modifier)
{
return modifier.apply(meta);
}
template <meta M, meta_modifier<M> FirstModifier, typename... RestModifiers>
static constexpr auto apply_modifiers(M meta, FirstModifier F, RestModifiers... rest)
{
return apply_modifiers(apply_modifier(meta, F), rest...);
}
template <meta M>
static constexpr auto apply_modifiers(M meta)
{
return meta;
}