简体   繁体   中英

Daisy chain variadic templated classes

I have a class template Pipeline:

template <typename A, typename B> class Pipeline;

I'd like to create a variadic function template accepting an arbitrary number of arbitrary Pipeline s, and I'd like to constrain those in a tricky way. Non variadic code would look like:

Pipeline<A, C> compose(Pipeline<A, B> p1, Pipeline<B, C> p2);
Pipeline<A, D> compose(Pipeline<A, B> p1, Pipeline<B, C> p2, Pipeline<C, D> p3);
// ...and so on

Now is it possible at all to constrain them end-to-end in a variadic way?

// I would like to write something like:
Pipeline<Args[0], Args[len(Args)-1]> compose(Pipeline<Args[i], Args[i+1]> ps...);

I'll assume that your Pipeline looks something like:

template <typename A, typename B>
struct Pipeline {
    using first = A;
    using second = B;
};

First, let's make a type trait for what's a valid chain link:

template <typename P1, typename P2>
struct is_valid_link : std::false_type { };

template <typename A, typename B, typename C>
struct is_valid_link<Pipeline<A,B>, Pipeline<B,C>> : std::true_type { };

Next, let's borrow @Columbo's bool_pack trick to verify that bunch of bool s are all true :

template <bool...> struct bool_pack;

template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;

And of course we need the index sequence trick:

template <typename... Pipelines,
          typename R = decltype(detail::daisy_chain(
                                    std::make_index_sequence<sizeof...(Pipelines)-1>(),
                                    std::declval<Pipelines>()...))
         >
R compose(Pipelines... pipelines)
{
    return {};
}

Where the bulk of the work is doing the checking here:

namespace detail {
    template <size_t... Is,
              typename... Pipelines,
              typename T = std::tuple<Pipelines...>,
              typename R = std::enable_if_t<
                          // ensure that all our pairwise pipelines are valid links
                                all_true<
                                    is_valid_link<std::tuple_element_t<Is,T>,
                                                  std::tuple_element_t<Is+1,T>>::value...
                                    >::value,
                           // pick out the first and last types
                                Pipeline<typename std::tuple_element_t<0, T>::first,
                                         typename std::tuple_element_t<sizeof...(Pipelines)-1, T>::second>
                            >>
    R daisy_chain(std::index_sequence<Is...>, Pipelines... pipelines);
}

That lets us do:

int main() {
    Pipeline<int, double> p = compose(Pipeline<int, char>{}, Pipeline<char, double>{});
}

The advantage of writing it this way is that you still have SFINAE - if that's something you want. Such that:

auto invalid = compose(Pipeline<int, char>{}, Pipeline<float, double>{});

will trigger an overload resolution failure:

main.cpp:46:31: error: no matching function for call to 'compose'
    Pipeline<int, double> p = compose(Pipeline<int, char>{}, Pipeline<float, double>{});
                              ^~~~~~~
main.cpp:40:3: note: candidate template ignored: substitution failure [with Pipelines = <Pipeline<int, char>, Pipeline<float, double>>]: no matching function for call to 'daisy_chain'
R compose(Pipelines... pipelines)
  ^
1 error generated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM