Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I constrain template parameter pack arguments to a "chain" sequence?

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

    • the first Functor must have Start as its first argument
    • the last Functor must have End as its second argument
    • each Functor's first argument is the second argument of the Functor preceding it

    E.g. Functor<A,B>, Functor<B, C>, Functor<C, D>, ... etc.

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:

  1. What is the best approach?
    • Can this be done with fold expression(s)?
    • Or concepts?
  2. How does one use std::is_convertible (or any of the other metaprogramming traits) on an un-specialized template class?
like image 901
SKNB Avatar asked Dec 06 '25 19:12

SKNB


2 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>>);

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

like image 191
Jarod42 Avatar answered Dec 08 '25 11:12

Jarod42


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).

like image 44
Ted Lyngmo Avatar answered Dec 08 '25 11:12

Ted Lyngmo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!