Multiple parameter packs in a single function?

时光毁灭记忆、已成空白 提交于 2021-01-27 07:31:55

问题


I'm trying to create a function that takes two parameter packs of objects. There are two templated base classes and I'd like to pass instances of derived classes to this function. Consider this example.

template <int N>
struct First {};

template <int N>
struct Second {};

// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};

template <int... firstInts, int... secondInts>
void function(float f, First<firstInts> &... first, Second<secondInts> &... second) {
  // ...
}

What I would like to do is call function like this

FirstImpl firstImpl;
OtherFirstImpl otherFirstImpl;
SecondImpl secondImpl;
OtherSecondImpl otherSecondImpl;
function(9.5f, firstImpl, otherFirstImpl, secondImpl, otherSecondImpl);

but this example won't compile. The compiler seems to be trying to pack everything into the second parameter pack and failing because FirstImpl can't be implicitly converted Second<N>.

How do I get around this?


回答1:


Let's first code a variable template which determines whether a type derives from First or not:

template <int N>
constexpr std::true_type is_first(First<N> const &) { return {}; }
template <int N>
constexpr std::false_type is_first(Second<N> const &) { return {}; }

template <class T>
constexpr bool is_first_v = decltype( is_first(std::declval<T>()) )::value;

And a struct Split which collects the indices of the First and Second types:

template <class, class, class, std::size_t I = 0> struct Split;

template <
    std::size_t... FirstInts,
    std::size_t... SecondInts,
    std::size_t N
>
struct Split<
    std::index_sequence<FirstInts...>,
    std::index_sequence<SecondInts...>,
    std::tuple<>,
    N
> {
    using firsts = std::index_sequence<FirstInts...>;
    using seconds = std::index_sequence<SecondInts...>;
};

template <
    std::size_t... FirstInts,
    std::size_t... SecondInts,
    std::size_t I, 
    typename T,
    typename... Tail
>
struct Split<
    std::index_sequence<FirstInts...>,
    std::index_sequence<SecondInts...>,
    std::tuple<T, Tail...>,
    I
> : std::conditional_t<
    is_first_v<T>,
    Split<std::index_sequence<FirstInts..., I>,
          std::index_sequence<SecondInts...>,
          std::tuple<Tail...>,
          I + 1
    >,
    Split<std::index_sequence<FirstInts...>,
          std::index_sequence<SecondInts..., I>,
          std::tuple<Tail...>,
          I + 1
    >
> {};

And like I told you in the comments, adding a member value to First and Second (or inheriting from std:integral_constant), this allows us to write the following:

template <std::size_t... FirstIdx, std::size_t... SecondIdx, typename Tuple>
void function_impl(float f, std::index_sequence<FirstIdx...>, std::index_sequence<SecondIdx...>, Tuple const & tup) {
    ((std::cout << "firstInts: ") << ... << std::get<FirstIdx>(tup).value) << '\n';
    ((std::cout << "secondInts: ") << ... << std::get<SecondIdx>(tup).value) << '\n';
    // your implementation
}

template <class... Args>
void function(float f, Args&&... args)  {
    using split = Split<std::index_sequence<>,std::index_sequence<>, std::tuple<std::decay_t<Args>...>>;
    function_impl(f, typename split::firsts{}, typename split::seconds{}, std::forward_as_tuple(args...));
}

Demo




回答2:


It's pretty much next to impossible to define something with two variadic parameter packs. Once a variadic parameter pack gets encountered, it likes to consume all remaining parameters, leaving no crumbs for the second pack to feed on.

However, as I mentioned, in many cases you can use tuples, and with deduction guides in C++17, the calling convention is only slightly longer than otherwise.

Tested with gcc 7.3.1, in -std=c++17 mode:

#include <tuple>

template <int N>
struct First {};

template <int N>
struct Second {};


template <int... firstInts, int... secondInts>
void function(std::tuple<First<firstInts>...> a,
          std::tuple<Second<secondInts>...> b)
{
}

int main(int, char* [])
{
    function( std::tuple{ First<4>{}, First<3>{} },
          std::tuple{ Second<1>{}, Second<4>{} });
}

That's the basic idea. In your case, you have subclasses to deal with, so a more sophisticated approach would be necessary, probably with an initial declaration of two tuples being just a generic std::tuple< First...> and std::tuple<Second...>, with some additional template-fu. Probably need to have First and Second declare their own type in a class member declaration, and then have the aforementioned template-fu look for the class member, and figure out which superclass it's dealing with.

But the above is the basic idea of how to designate two sets of parameters, from a single variadic parameter list, and then work with it further...




回答3:


Why won't you simply pass the class itself as template parameter? Like this:

template <int N>
struct First {};

template <int N>
struct Second {};

// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};

template <typename FirstSpec, typename SecondSpec>
void function(float f, FirstSpec & first, SecondSpec & second) {
  // ...
}



回答4:


Not exactly what you asked but... you could unify the two list using a variadic template-template int container (Cnt, in the following example) and next detect, for every argument, if is a First or a Second (see the use of std::is_same_v)

The following is a full working example

#include <string>
#include <vector>
#include <iostream>
#include <type_traits>

template <int>
struct First {};

template <int>
struct Second {};

// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};

template <template <int> class ... Cnt, int... Ints>
void function (float f, Cnt<Ints> & ... args)
 {
    (std::cout << ... << std::is_same_v<Cnt<Ints>, First<Ints>>);
 }

int main()
 {
   FirstImpl firstImpl;
   FirstImpl otherFirstImpl;
   SecondImpl secondImpl;
   SecondImpl otherSecondImpl;
   function(9.5f, firstImpl, otherFirstImpl, secondImpl, otherSecondImpl);
 }


来源:https://stackoverflow.com/questions/48739516/multiple-parameter-packs-in-a-single-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!