Variadic function template with pack expansion not in last parameter

做~自己de王妃 提交于 2019-12-17 04:08:11

问题


I am wondering why the following code doesn't compile:

struct S
{
    template <typename... T>
    S(T..., int);
};

S c{0, 0};

This code fails to compile with both clang and GCC 4.8. Here is the error with clang:

test.cpp:7:3: error: no matching constructor for initialization of 'S'
S c{0, 0};
  ^~~~~~~
test.cpp:4:5: note: candidate constructor not viable: requires 1 argument, but 2 were provided
    S(T..., int);
    ^

It seems to me that this should work, and T should be deduced to be a pack of length 1.

If the standards forbids doing things like this, does anyone know why?


回答1:


Because when a function parameter pack is not the last parameter, then the template parameter pack cannot be deduced from it and it will be ignored by template argument deduction.

So the two arguments 0, 0 are compared against , int, yielding a mismatch.

Deduction rules like this need to cover many special cases (like what happens when two parameter packs appear next to each other). Since parameter packs are a new feature in C++11, the authors of the respective proposal drafted the rules conservatively.

Note that a trailing template parameter pack will be empty if it is not otherwise deduced. So when you call the constructor with one argument, things will work (notice the difference of template parameter pack and function parameter pack here. The former is trailing, the latter is not).




回答2:


So, there should be a workaround. Something along these lines:

namespace v1 {
  // Extract the last type in a parameter pack.
  // 0, the empty pack has no last type (only called if 1 and 2+ don't match)
  template<typename... Ts>
  struct last_type {};

  // 2+ in pack, recurse:
  template<typename T0, typename T1, typename... Ts>
  struct last_type<T0, T1, Ts...>:last_type<T1, Ts...>{};

  // Length 1, last type is only type:
  template<typename T0>
  struct last_type<T0> {
    typedef T0 type;
  };
}
namespace v2 {
  template<class T> struct tag_t{using type=T;};
  template<class T> using type_t = typename T::type;
  template<class...Ts>
  using last = type_t< std::tuple_element_t< sizeof...(Ts)-1, std::tuple<tag_t<Ts>...> > >;
  template<class...Ts>
  struct last_type {
    using type=last<Ts...>;
  };
}
template<class...Ts>
using last_type=v2::late_type<Ts...>; // or v1   


struct S
{
    // We accept any number of arguments
    // So long as the type of the last argument is an int
    // probably needs some std::decay to work right (ie, to implicitly work out that
    // the last argument is an int, and not a const int& or whatever)
    template <typename... T, typename=typename std::enable_if<std::is_same<int, typename last_type<T...>::type>>::type>
    S(T...);

};

where we check that the last type of a parameter pack is an int, or that we where only passed an int.




回答3:


From the working draft of the standard N3376 § 14.1 is a probable section to read about this.

Below is § 14.1.11

If a template-parameter of a class template or alias template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. If a template-parameter of a primary class template or alias template is a template parameter pack, it shall be the last template-parameter. A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument.




回答4:


I am actually a little interested in the same thing (wanting to specialize templated parameter packs based on the final arguments).

I believe there may be a path forward by combining tuple reversal (std::make_tuple, back-port std::apply for C++14, etc):

  • How to reverse the order of arguments of a variadic template function?

Will get back on here if it is successful.

Related posts:

  • Parameters after parameter pack in function
  • Parameter with non-deduced type after parameter pack

EDIT: Yup, figured it out after a bit; not perfect, as there are extra copies flying around, but it's a start.

If you know a simpler way than what I list below, please don't hesitate to post!

TL;DR

Can do stuff like this:

auto my_func_callable = [] (auto&& ... args) {
    return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
    stdcustom::make_callable_reversed(my_func_callable);

And then implement this pseduo code:

template<typename ... Args>
void my_func(Args&& ... args, const my_special_types& x);

By doing something like:

template<typename... Args>
void my_func(Args&& ... args)
    -> call my_func_reversed(args...)
template<typename... RevArgs>
void my_func_reversed(const my_special_types& x, RevArgs&&... revargs)
    -> do separate things with revargs and my_special_types
    -> sub_func_reversed(revargs...)

Using the above utilities.

Has some (a lot of) drawbacks. Will list them below.

Scope

This is for users of C++14 (maybe C++11), who want to borrow from the future (C++17).

Step 1: Reverse arguments

There are a few different ways to do this. I've listed out some alternatives in this example:

  • tuple.cc - Playground for two alternatives (credits in the source code):
    1. Use foldable expressions and manipulate the index passed via std::apply_impl (credit: Orient).
    2. Use recursive templates to construct a reversed index_sequence (credit: Xeo)
  • tuple.output.txt - Example output

    • This prints out the reversed_index_sequence template from Xeo's example. I needed this for debugging.

      >>> name_trait<std::make_index_sequence<5>>::name()
      std::index_sequence<0, 1, 2, 3, 4>
      >>> name_trait<make_reversed_index_sequence<5>>::name()
      std::index_sequence<4, 3, 2, 1, 0>
      

I chose Alternative 1, as it's easier for me to digest. I then tried to formalize it right quick:

  • tuple_future.h - Borrowing from the future (namespace stdfuture), and making an extension (namespace stdcustom)
  • tuple_future_main.cc - Simple, advanced, and useful (see below) examples using the above
  • tuple_future_main.output.txt - Example output

Definition Snippets (adaptation of C++17 possible implementation of std::apply on cppreference.com):

namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_reversed_impl(F &&f,
    Tuple &&t, std::index_sequence<I...>) 
{
    // @ref https://stackoverflow.com/a/31044718/7829525
    // Credit: Orient
    constexpr std::size_t back_index = sizeof...(I) - 1;
    return f(std::get<back_index - I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply_reversed(F &&f, Tuple &&t) 
{
    // Pass sequence by value to permit template inference
    // to parse indices as parameter pack
    return detail::apply_reversed_impl(
        std::forward<F>(f), std::forward<Tuple>(t),
        std::make_index_sequence<
            std::tuple_size<std::decay_t<Tuple>>::value>{});
}

Usage Snippets: (from tuple_future_main.output.txt, copied from above)

auto my_func_callable = [] (auto&& ... args) {
    return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
    stdcustom::make_callable_reversed(my_func_callable);

Step 2: Buckle your shoe (with reversed parameter packs)

First, establish the patterns for the final arguments that you wish to use. You will have to explicitly enumerate these, as you can only have one parameter pack.

(Taken from tuple_future_main.cc):

Example Scenario:

We like to add things to containers with a name, something of the form:

add_item(const Item& item, const string& name, Container& c)

We can also construct an Item with a [awfully large] number of overloads, and we have convenenience overloads:

add_item(${ITEM_CTOR_ARGS}, const string& name, Container& c)

To do so, we can declare the following:

void add_item_direct(const Item& item, const string& name, Container& c)
Item create_item(Args&&... args)

And then define our generic interfaces:

template<typename... Args>
void add_item(Args&&... args) {
    ...
    auto reversed = stdcustom::make_callable_reversed(callable);
    reversed(std::forward<Args>(args)...);
}
template<typename ... RevArgs>
void add_item_reversed(Container& c, const string& name, RevArgs&&... revargs)
{
    ...
    static auto ctor = VARIADIC_CALLABLE(create_item,);
    ...
    auto item = ctor_reversed(std::forward<RevArgs>(revargs)...);
    add_item_direct(item, name, c);
}

Now we can do stuff like: (taken from tuple_future_main.output.txt)

>>> (add_item(Item("attribute", 12), "bob", c));
>>> (add_item("attribute", 12, "bob", c));
>>> (add_item(Item(2, 2.5, "twelve"), "george", c));
>>> (add_item(2, 2.5, "twelve", "george", c));
>>> (add_item(Item(2, 15.), "again", c));
>>> (add_item(2, 15., "again", c));
>>> c
bob - ctor3: ctor3: ctor1: attribute (12, 10)
bob - ctor3: ctor1: attribute (12, 10)
george - ctor3: ctor3: ctor2: 2, 2.5 (twelve)
george - ctor3: ctor2: 2, 2.5 (twelve)
again - ctor3: ctor3: ctor2: 2, 15 ()
again - ctor3: ctor2: 2, 15 ()

Note the extra copy constructors... :(

Drawbacks

  • Ugly as hell
  • May not be useful
    • It could be easier to just refactor your interfaces
      • However, this could be used as a stop-gap to transition to a more generalized interface.
      • Possibly fewer lines to delete.
    • Especially if it plugs your development process with template explosions
  • Can't nail down where the extra copies are coming from.
    • It may be due to judicious usage of variadic lambdas
  • You have to carefully craft your base functionality
    • You shouldn't try to extend an existing function.
    • Parameter packs will be greedy in how they match to functions
    • You either need to explicitly spell out each overload you want, or bow down and let the variadic parameter pack dispatch to your desired functionality
      • If you find an elegant way around this, please let me know.
  • Template errors are shitty.
    • Granted, not too shitty. But it's hard to infer that you missed an available overload.
  • Wraps a lot of simple functionality in lambdas
    • You may be able to use make_reversed_index_sequence and directly dispatch to the function (mentioned in other SO posts). But that's painful to repeat.

Todo

  • Get rid of extra copies
  • Minimize the need for all the lambdas
    • Not necessary if you have a Callable
  • Try to combat parameter pack greediness

    • Is there a generalized std::enable_if matching that matches to both lvalue- and rvalue-references, and possibly handle forwarding compatible implicit copy constructors?

      template<typename ... Args>
      void my_func(Args&& ... args) // Greedy
      void my_func(magical_ref_match<string>::type, ...)
          // If this could somehow automatically snatch `const T&` and `T&&` from the parameter pack...
          // And if it can be used flexible with multiple arguments, combinatorically
      

Hopes

  • Maybe C++17 will support non-final parameter pack arguments, such that all of this can be discarded... fingers crossed


来源:https://stackoverflow.com/questions/14768951/variadic-function-template-with-pack-expansion-not-in-last-parameter

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