Reputation: 52739
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?
Upvotes: 23
Views: 6164
Reputation: 275230
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
.
Upvotes: 6
Reputation: 2004
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):
Will get back on here if it is successful.
Related posts:
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!
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.
This is for users of C++14 (maybe C++11), who want to borrow from the future (C++17).
There are a few different ways to do this. I've listed out some alternatives in this example:
std::apply_impl
(credit: Orient).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:
namespace stdfuture
), and making an extension (namespace stdcustom
)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);
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... :(
make_reversed_index_sequence
and directly dispatch to the function (mentioned in other SO posts). But that's painful to repeat.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
Upvotes: 3
Reputation: 21317
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.
Upvotes: 2
Reputation: 506847
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).
Upvotes: 11