简体   繁体   中英

Split variadic parameter pack using template specialization

I'm trying to define a sort of template 'map' primitive (as in map-reduce). The idea is that I want to apply a function to every item of a template parameter pack. The function can be any callable object. It can return any type (though the return type will be ignored), and it can take additional arguments on top of the item in question.

The tricky part is that I effectively have two parameter packs that I need to deal with. They'll initially be packed together, but I want to split them using template specialization. What follows is my attempt at doing this.

In case it isn't obvious (due to the auto keyword in the template parameter list), this is using C++17.

#include <utility>

template <class Signature, auto f, class... ArgsAndItems>
struct Map;

template
<
    class ReturnType,
    class Item,
    class... ArgumentTypes,
    auto f,
    class... Items
>
struct Map
<
    ReturnType (Item, ArgumentTypes...),
    f,
    ArgumentTypes...,
    Item,
    Items...
>
{
    static void
    function (ArgumentTypes &&... arguments, Item && item, Items &&... items);
};

template <class ReturnType, class Item, class... ArgumentTypes, auto f>
struct Map<ReturnType (Item, ArgumentTypes...), f, ArgumentTypes...>
{
    static void
    function (ArgumentTypes &&... arguments);
};

template
<
    class ReturnType,
    class Item,
    class... ArgumentTypes,
    auto f,
    class... Items
>
void
Map
<
    ReturnType (Item, ArgumentTypes...),
    f,
    ArgumentTypes...,
    Item,
    Items...
>::function (ArgumentTypes &&... arguments, Item && item, Items &&... items)
{
    f (std::forward<Item> (item), std::forward<ArgumentTypes> (arguments)...);
    Map
    <
        ReturnType (Item, ArgumentTypes ...),
        f,
        ArgumentTypes...,
        Items...
    >::function
    (
        std::forward<ArgumentTypes> (arguments)...,
        std::forward<Items> (items)...
    );
}

template <class ReturnType, class Item, class... ArgumentTypes, auto f>
void
Map
<
    ReturnType (Item, ArgumentTypes...),
    f,
    ArgumentTypes...
>::function (ArgumentTypes &&... arguments)
{
}

The idea is to have a wrapper that looks something like

template <auto f, class ... ArgsAndItems>
void
map (ArgsAndItems && ... args_and_items)
{
    Map
    <
        decltype (decltype (f)::operator ()),
        f,
        ArgsAndItems...
    >::function (std::forward <ArgsAndItems> (args_and_items) ...);
}

Which I would then use as

map <foo> (args_for_foo..., items_to_map_over...);

Unfortunately, when I try to compile this (using clang++), I get the following error.

map.hpp:14:8: error: class template partial specialization contains template
      parameters that cannot be deduced; this partial specialization will never
      be used
      [-Wunusable-partial-specialization]
struct Map
       ^~~
map.hpp:8:8: note: non-deducible template parameter 'ReturnType'
        class ReturnType,
              ^
map.hpp:9:8: note: non-deducible template parameter 'Item'
        class Item,
              ^
map.hpp:10:11: note: non-deducible template parameter 'ArgumentTypes'
        class... ArgumentTypes,
                 ^
map.hpp:11:7: note: non-deducible template parameter 'f'
        auto f,
             ^
map.hpp:12:11: note: non-deducible template parameter 'Items'
        class... Items
                 ^
1 error generated.

Now, I wouldn't be surprised if it doesn't like the fact that ArgumentTypes... shows up twice in my specializations, though it isn't saying so directly.

What exactly is going wrong, and how might I build my map primitive in a way that avoid this? I don't want to store copies or references to any of the arguments, because that shouldn't be necessary. If I were manually writing a template that specialized the function and parameter types, I wouldn't need to store anything. This rules out tuple wrappers as an option.

EDIT: Added usage information, as requested.

EDIT: Fixed overzealous use of rvalue reference qualifiers, to avoid confusion.

What exactly is going wrong, [...]

There are several issues so it may be easier to start with a simple base example that works (no perfect forwarding).

[...] and how might I build my map primitive in a way that avoid this?

You can separate the parameter packs:

  • argument types are passed as template arguments for Map
  • item types are passed as template arguments for Map::function

Here is a working example without perfect forwarding. It is not complete regarding the deduction of the argument types because of possible cv-qualifiers and ref-qualifiers.

#include <iostream>

template<class F, class... Args>
struct Map {
  template<class... Items>
  static void function(Args... args, Items... items) {
    static constexpr auto f = F{};

    // here comes a fold expression
    // see https://en.cppreference.com/w/cpp/language/fold

    ( f(items, args...), ...); // "fold over comma operator"
  }
};

////////////////////////////////////////////////////////////////////////////////

template<class F, class Ret, class Item, class... Args, class... ArgsItems>
void map_impl(Ret(F::*)(Item, Args...) const, ArgsItems... args_items) {
  Map<F, Args...>::function(args_items...);
}

template<class F, class... ArgsItems>
void map(ArgsItems... args_items) {
  map_impl<F>(&F::operator(), args_items...);
}

////////////////////////////////////////////////////////////////////////////////

struct print_x_m_plus_n {
  void operator()(int x, int m, int n) const {
    int y = x * m + n;
    std::cout << y << std::endl;
  }
};

int main() {
  constexpr int m = 2;
  constexpr int n = 1;

  map<print_x_m_plus_n>(m, n, 0, 1, 2);
}

Output:

1
3
5

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