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!
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:
I chose Alternative 1, as it's easier for me to digest.
I then tried to formalize it right quick:
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
Hopes
- Maybe C++17 will support non-final parameter pack arguments, such that all of this can be discarded... fingers crossed