Suppose I have two classes:
template <typename X, typename Y>
class Functor {};
template <typename Start, typename End, typename ...Functors>
class Template {};
Template has the constraints:
All Functors must be type Functor
All Functor must be in a chain sequence, such that
Functor must have Start as its first argumentFunctor must have End as its second argumentFunctor's first argument is the second argument of the Functor preceding itE.g. Functor<A,B>, Functor<B, C>, Functor<C, D>, ... etc.
Starting with: char
Ending with: long
Template<char, long, Functor<char, A>, Functor<A, B>, Functor<B, C>, Functor<C, long>> t;
1 2 3 4
├─────────┼─────────┼─────────┼─────────┤
argument: char A B C long
Functor #
= 1 Functor<char, A>,
2 Functor<A, B>,
3 Functor<B, C>,
4 Functor<C, long>
namespace ns
{
template <typename X, typename Y = X>
class Functor
{
public:
using first = X;
using second = Y;
Functor(X lVal) : x(lVal) {}
private:
X x;
};
template <typename Start, typename End, typename ...Functors>
requires(std::is_convertible_v<Functors, Functor> && ...) //error
class Template
{
// How does one use `std::is_convertible_v` on
// an un-specialized template class?
};
template <typename Start, typename End>
class Template<Start, End, Functor<Start, End>>
{};
}
Questions:
std::is_convertible (or any of the other metaprogramming traits) on an un-specialized template class?With (ab)use of operator overloading, you might do
// Used std::type_identity as wrapper
// Operator+, but no implementation needed
template <typename A, typename B, typename C>
std::type_identity<Functor<A, C>>
operator +(std::type_identity<Functor<A, B>>, std::type_identity<Functor<B, C>>);
And then just check the operation is "valid".
template <typename Start, typename End, typename... Functors>
requires(std::is_same_v<std::type_identity<Functor<Start, End>>,
decltype((std::type_identity<Functors>{} + ...))>)
class Template {
//...
};
Demo
You could start by adding a type trait for checking that a template parameter is of Functor type:
template <typename X, typename Y>
class Functor {
public:
using first_type = X;
using second_type = Y;
};
// type trait to check if a type is a Functor
template <class...>
struct is_Functor : std::false_type {};
template <class X, class Y>
struct is_Functor<Functor<X, Y>> : std::true_type {};
template <class T>
inline constexpr bool is_Functor_v = is_Functor<T>::value;
Then require that all of them are in fact of Functor type using a requires clause with a fold expression:
// Require Functors
template <typename Start, typename End, typename... Functors>
requires(is_Functor_v<Functors> && ...)
class Template {
Then assert that the first Functor has Start as first parameter and that the last Functor has End as the second parameter:
// helper type to get a Functor (type) at a specific index:
template <std::size_t I>
using funcat = typename std::tuple_element_t<I, std::tuple<Functors...>>;
static_assert(std::is_same_v<
Start, typename funcat<0>::first_type>);
static_assert(std::is_same_v<
End, typename funcat<sizeof...(Functors) - 1>::second_type>);
Then checking that the second parameter of each Functor is the same type as the first parameter of the next Functor using a lambda and fold expression:
static_assert([]<std::size_t... I>(std::index_sequence<I...>) {
return (... && std::is_same_v<typename funcat<I>::second_type,
typename funcat<I + 1>::first_type>);
}(std::make_index_sequence<sizeof...(Functors) - 1>()));
You could also make a concept out of the is_Functor type trait if you prefer this declaration:
template <class T>
concept functor = is_Functor_v<T>;
template <typename Start, typename End, functor... Functors>
class Template {
//
};
Demo
As for the second question, "How does one use std::is_convertible (or any of the other metaprogramming traits) on an un-specialized template class?", you could again create a type trait or concept that checks if a Functor can be instantiated by the supplied type without having to explicitly supply any template parameters.
Example concept:
template <class F, template <class...> class T>
concept deducible = requires(F&& f) {
T(std::forward<F>(f));
};
And the class template would then need to use the Functor type each actual template parameter would be converted to:
template <typename Start, typename End, deducible<Functor>... Functors>
class Template {
template <std::size_t I>
using funcat =
std::tuple_element_t<I,
std::tuple<decltype(Functor(std::declval<Functors>()))...>>;
// converted to Functor ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
static_assert(std::is_same_v<
Start, typename funcat<0>::first_type>,
"First Functor::first_type != Start");
static_assert(std::is_same_v<
End, typename funcat<sizeof...(Functors) - 1>::second_type>,
"Last Functor::second_type != End");
static_assert([]<std::size_t... I>(std::index_sequence<I...>) {
return (... && std::is_same_v<typename funcat<I>::second_type,
typename funcat<I + 1>::first_type>);
}(std::make_index_sequence<sizeof...(Functors) - 1>()),
"Functor_n<..., T> != Functor_n+1<T, ...>");
};
Demo where a std::pair<X, Y> is convertible into a Functor<X, Y> (odd conversion, but it's just an example).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With