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 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:
- What is the 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 {
//...
};
1
-
It seems that the second overload is not needed, just check whether the result type of
(Functors + ...)
isFunctor<Start, End>
, demo.– 康桓瑋3 hours ago
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. 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