Supposing 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 typeFunctor
-
All
Functor
must be in a chain sequence, such that- the first
Functor
must haveStart
as its first argument - the last
Functor
must haveEnd
as its second argument - each
Functor
‘s first argument is the second argument of theFunctor
preceding it
E.g.
Functor<A,B>, Functor<B, C>, Functor<C, D>, ...
etc. - the first
Example:
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>
Code
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:
- Best approach?
- Can this be done with fold expression(s)?
- Or concepts?
- How does one use
std::is_convertible
(or any of the other metaprogramming traits) on an un-specialized template class?
If you’ve made it this far, thank you for your time, and thank you in advance for any light you can shed.
0
3 Answers
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>>);
template <typename A, typename B>
std::type_identity<A>
operator +(std::type_identity<Functor<A, B>>, std::type_identity<B>);
And then just check the operation is "valid".
template <typename Start, typename End, typename... Functors>
requires(std::is_same_v<std::type_identity<Start>,
decltype((std::type_identity<Functors>{} + ...)
+ std::type_identity<End>{})>)
class Template {
//...
};
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 {
//
};
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 that checks if a Functor
can be instantiated by the supplied type without having to explicitly supply any template parameters.
Example:
template <class T>
struct is_convertible_to_Functor {
static std::false_type test(...);
template <class U = T>
static auto test(U) -> decltype(Functor(std::declval<U>()), std::true_type{});
static constexpr bool value = decltype(test(std::declval<T>()))::value;
};
template<class T>
concept convertible_to_Functor = is_convertible_to_Functor<T>::value;
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, convertible_to_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>);
static_assert(std::is_same_v<
End, typename funcat<sizeof...(Functors) - 1>::second_type>);
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>()));
};
Demo where a std::pair<X, Y>
is convertible into a Functor<X, Y>
(odd conversion, but it’s just an example).
This answers the main question (one question per Stackoverflow question, please, you can post a separate question for all the others). The following seems to work. This gives the basic checking of proper sequence of Functor
s, and turning this into a concept, or adding a type traits-style using
wrapper, etc…, can be left as a homework assignment:
class Start;
class End;
template <typename X, typename Y> class Functor {};
template<typename required, typename NextFunctor,
typename ...RemainingFunctors> struct FunctorValidate;
template<typename required>
struct FunctorValidate<required, Functor<required, End>> {
typedef void type_t;
};
template<typename required, typename NextFunctorType,
typename FirstRemainingFunctor, typename ...RemainingFunctors>
struct FunctorValidate<required, Functor<required, NextFunctorType>,
FirstRemainingFunctor, RemainingFunctors...>
: FunctorValidate<NextFunctorType, FirstRemainingFunctor,
RemainingFunctors...>
{
};
template<typename ...AllFunctors> struct FunctorChain
: FunctorValidate<Start, AllFunctors...>
{
};
class A;
class B;
class C;
typedef FunctorChain<Functor<Start, A>,
Functor<A, B>,
Functor<B, C>,
Functor<C, End>>::type_t ok1;
typedef FunctorChain<Functor<Start, End>>::type_t ok2;
#if 0
typedef FunctorChain<Functor<A, B>,
Functor<B, C>,
Functor<C, End>>::type_t error1;
typedef FunctorChain<Functor<Start, A>,
Functor<A, B>,
Functor<B, C>>::type_t error2;
typedef FunctorChain<Functor<Start, A>,
Functor<B, C>,
Functor<C, End>>::type_t error3;
#endif