Related
I know it's possible to use the C++ type system to generate a sorted type list from an existing tuple type.
Examples of doing this can be found at:
https://codereview.stackexchange.com/questions/131194/selection-sorting-a-type-list-compile-time
How to order types at compile-time?
However, is it possible to do a compile-time sort of a heterogeneous tuple by value? For example:
constexpr std::tuple<long, int, float> t(2,1,3);
constexpr std::tuple<int, long, float> t2 = tuple_sort(t);
assert(t2 == std::tuple<int, long, float>(1,2,3));
My assumption is this is not possible, since you'd have to conditionally generate new tuple types based on the result of comparing values. Even if the comparison function uses constexpr, it would seem that this can't work.
However, an offhand comment from this answer indicates it somehow is possible to do this, just very difficult:
I lied. You can do it if the values and the compare function are
constexpr, but the code to pull it off will be huge and not worth the
time to write.
So is this comment correct? How could this even be conceptually possible, given the way the C++ type system works.
To preface the answer, it might be vastly more straightforward to use Boost.Hana. The prerequisite for Hana is that your comparison produces a compile-time answer. In your case, this would require a Hana tuple containing compile-time versions of these basic data types, similar to std::integral_constant. If it's acceptable to have your tuples' values encoded entirely in their types, Hana makes this trivial.
I believe it would be possible to do this directly once you can use a tuple as a non-type template parameter in C++20. Until then, you can get pretty close (live example):
int main() {
constexpr std::tuple<long, int, float> t(2,1,3);
call_with_sorted_tuple(t, [](const auto& sorted) {
assert((sorted == std::tuple<int, long, float>(1,2,3)));
});
}
As far as I know, it is impossible to return the sorted tuple directly; the callback approach is required because it is instantiated with every possible tuple type and only the correct one is actually run. This means there is significant compile-time overhead to this approach. The compile times grow quickly with small tuple size increases.
Now, how does this actually work? Let's get the magic out of the way—converting a runtime integral value into a compile-time one. This can fit well into its own header, and is shamelessly stolen from P0376:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0376r0.html
#include <array>
#include <type_traits>
#include <utility>
// A function that invokes the provided function with
// a std::integral_constant of the specified value and offset.
template <class ReturnType, class T, T Value, T Offset, class Fun>
constexpr ReturnType invoke_with_constant_impl(Fun&& fun) {
return std::forward<Fun>(fun)(
std::integral_constant<T, Value + Offset>());
}
// Indexes into a constexpr table of function pointers
template <template <class...> class ReturnTypeDeducer,
class T, T Offset, class Fun, class I, I... Indices>
constexpr decltype(auto) invoke_with_constant(Fun&& fun, T index,
std::integer_sequence<I, Indices...>) {
// Each invocation may potentially have a different return type, so we
// need to use the ReturnTypeDeducer to figure out what we should
// actually return.
using return_type
= ReturnTypeDeducer<
decltype(std::declval<Fun>()(std::integral_constant<T, Indices + Offset>()))...>;
return std::array<return_type(*)(Fun&&), sizeof...(Indices)>{
{{invoke_with_constant_impl<return_type, T, Indices, Offset, Fun>}...}}
[index - Offset](std::forward<Fun>(fun));
}
template <class T, T BeginValue, T EndValue>
struct to_constant_in_range_impl {
// Instantiations of "type" are used as the Provider
// template argument of argument_provider.
template <class U>
struct type
{
template <template <class...> class ReturnTypeDeducer, class Fun, class Self>
static constexpr decltype(auto) provide(Fun&& fun, Self&& self) {
return invoke_with_constant<ReturnTypeDeducer, T, BeginValue>(
std::forward<Fun>(fun),
std::forward<Self>(self).value,
std::make_index_sequence<EndValue - BeginValue>());
}
U&& value;
};
};
Now one thing to note is that I use C++20's ability to give lambdas template parameters simply because compilers already support this and it makes turning index_sequences into parameter packs really easy. The long way to do this is available prior to C++20, but a bit of an eyesore on top of code that's already hard enough to get through.
The sort itself isn't too bad despite the tuple needing compile-time indices for std::get (unless you reuse the above magic, but all I have to say to that is yikes). You can change the algorithm as needed. You can even use a regular std::vector in C++20 and push indices onto the back. What I chose to do is generate a std::array containing the sorted-order indices of the tuple:
// I had trouble with constexpr std::swap library support on compilers.
template<typename T>
constexpr void constexpr_swap(T& a, T& b) {
auto temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}
template<std::size_t I>
using index_c = std::integral_constant<std::size_t, I>;
template<typename... Ts>
constexpr auto get_index_order(const std::tuple<Ts...> tup) {
return [&]<std::size_t... Is>(std::index_sequence<Is...> is) {
std::array<std::size_t, sizeof...(Is)> indices{Is...};
auto do_swap = [&]<std::size_t I, std::size_t J>(index_c<I>, index_c<J>) {
if (J <= I) return;
if (std::get<I>(tup) < std::get<J>(tup)) return;
constexpr_swap(indices[I], indices[J]);
};
auto swap_with_min = [&]<std::size_t I, std::size_t... Js>(index_c<I> i, std::index_sequence<Js...>) {
(do_swap(i, index_c<Js>{}), ...);
};
(swap_with_min(index_c<Is>{}, is), ...);
return indices;
}(std::index_sequence_for<Ts...>{});
}
The main idea here is obtaining a pack of indices from 0 to N-1 and then dealing with each individually. Rather than trying to generate a second pack from I+1 to N-1, I took the easy road and reused the 0 to N-1 pack I already had, ignoring all out-of-order combinations when swapping. The dance with index_c is to avoid calling the lambdas via awkward lambda.template operator()<...>(...) syntax.
Now we have the indices of the tuple in sorted order and magic to convert one index to one with its value encoded in the type. Rather than build the magic to handle multiple values, I took the probably-suboptimal approach to build on the support for one at a time by making a recursive function:
template<typename... Ts, typename F, std::size_t... Converted>
constexpr void convert_or_call(const std::tuple<Ts...> tup, F f, const std::array<std::size_t, sizeof...(Ts)>& index_order, std::index_sequence<Converted...>) {
using Range = typename to_constant_in_range_impl<std::size_t, 0, sizeof...(Ts)>::template type<const std::size_t&>;
if constexpr (sizeof...(Converted) == sizeof...(Ts)) {
f(std::tuple{std::get<Converted>(tup)...});
} else {
Range r{index_order[sizeof...(Converted)]};
r.template provide<std::void_t>([&]<std::size_t Next>(index_c<Next>) {
convert_or_call(tup, f, index_order, std::index_sequence<Converted..., Next>{});
}, r);
}
}
I would have made this a lambda to avoid repeating captures, but as its recursive, it needs a workaround to call itself in lambda form. I'd be happy to hear of a good, constexpr-compatible solution for lambdas in this case that takes into account the fact that the lambda's template arguments differ each call.
Anyway, this is the use of the magic. We want to call this a total of N times, where N is the tuple size. That's what the if constexpr checks for, and finally delegates to the function passed from main, easily building a new tuple from the compile-time index order sequence. To recurse, we add on this compile-time index to a list we build up.
Finally, since what should have been a lambda is its own function, the function called from main is a simple wrapper that gets the index-order array and starts off the runtime-sequence-to-compile-time-sequence recursion with nothing converted to start:
template<typename... Ts, typename F>
constexpr void call_with_sorted_tuple(const std::tuple<Ts...>& tup, F f) {
auto index_order = get_index_order(tup);
convert_or_call(tup, f, index_order, std::index_sequence<>{});
}
I believe, it can not be done.
The essential part of any sorting would be to use tuple value in if constexpr context, but since function arguments are not constexpr, they can not appear in if constexpr.
And since tuples can't be non-type template arguments, template-based solution can't be implemented either. Unless we make tuple of type-encoded values (like std::integral_constant) I believe the solution is not available.
Return type cannot depend of value of parameters (even more as parameter cannot be constexpr) of a function, so
constexpr std::tuple<long, int, float> t1(2, 1, 3);
constexpr std::tuple<long, int, float> t2(3, 2, 1);
static_assert(std::is_same<decltype(tuple_sort(t1), decltype(tuple_sort(t2)>::value, "!");
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?
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).
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.
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):
Use foldable expressions and manipulate the index passed via std::apply_impl (credit: Orient).
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
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.
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?
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).
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.
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):
Use foldable expressions and manipulate the index passed via std::apply_impl (credit: Orient).
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
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.
Is there a portable way to generate a std::tuple (really a std::array) from the contents of a container? Such a tuple would allow std::apply to extract function arguments from a container.
My first attempt, using tail recursion, fails with compiler error: "recursive template instantiation exceeds maximum...".
I could not quite get my second attempt (std::for_each with a mutable lambda holding tuple) to compile with desired results.
I assume that something along the lines of how boost::mpl handles variadic metafunctions (ie magic using boost::preprocessor) could be made to work -- but that's so c++03. I'm hoping there's a better solution.
The function signature would look something like:
std::list<int> args_as_list = {1, 2, 3, 4};
auto tpl = args_as_tuple(args_as_list);
where type of tpl is std::array<int const, 4>.
Short answer: no, it is not possible.
Explanation: both std::tuple and std::array require compile-time information about number of elements. std::list or std::vector can provide only runtime information about elements count.
your args_as_tuple function would have to be a template, taking number of expected arguments as template argument (args_as_tuple<4>(args_as_list)).
Although having to put number of arguments as template argument seems harsh, but in case of your example it's is quite obvious - number of function arguments (function provided to std::apply) has to be known on compile-time as well.
For more generic code you could use: function-traits or code from this answer.
Or use std::array from begging instead of std::list (a lot of generic template code, but good compile time checks)
The number of elements in a std::tuple, or a std::array, is part of its type information. Therefore, your function args_as_tuple proposed above would have to be a template somehow, and each different possible size of the result is going to require a different instantiation of that template. Therefore, you cannot make a program that can support arbitrarily many sizes of tuples unless the code of that program is infinite (not possible).
If you only care about the range of values of int, say, you could instantiate the template 4 billion times, but then your executable is going to be at least 4 gigabytes large.
If you really only care about a few different sizes of vectors in your actual program, you could instantiate just those templates and write conversion code that cases out on the value of std::list::size() and calls the appropriate function (tedious).
But your exact code snippet
std::list<int> args_as_list = {1, 2, 3, 4};
auto tpl = args_as_tuple(args_as_list);
can never work in C++. Because, in C++ all variables have to have a known type determined at compile time. Even though you are using keyword auto, that auto has to get resolved at compile time to a fixed type, which means a fixed size if it's a tuple or array, no matter what sort of template shenanigans the expression args_as_tuple is doing.
Since my problem can't be solved, I solved a slightly different one which allowed me to move on.
I came up with a solution which allows me to extract arguments for a functor from a container. I am able to instantiate a eval_container with the functor I want evaluated, then pass the container to the resulting object.
#include <utility>
template <int N>
using Int = std::integral_constant<int, N>;
template <typename T>
struct arity : arity<decltype(&T::operator())> {};
template <typename T, typename RT, typename...Args>
struct arity<RT(T::*)(Args...) const>
{
// could enforce maximum number of arguments
static constexpr int value = sizeof...(Args);
};
template <typename F, int N = arity<F>::value>
struct eval_container
{
eval_container(F const& f) : f(f) {}
eval_container(F&& f) : f(std::move(f)) {}
template <typename Iter, typename I, typename...Args>
auto operator()(Iter&& iter, I, Args&&...args) const
{
// assert(iter != end)
auto&& arg = *iter++;
return (*this)(std::forward<Iter>(iter)
, Int<I()-1>{}
, std::forward<Args>(args)...
, arg);
}
template <typename Iter, typename...Args>
auto operator()(Iter&&, Int<0>, Args&&...args) const
{
// assert(iter == end)
return f(std::forward<Args>(args)...);
}
template <typename C>
auto operator()(C const& container) const
{
return (*this)(container.begin(), Int<N>{});
}
F f;
};
}
To keep things generic and straightforward, say that I have a std::vector of integers, such as:
std::vector<int> v;
Now, what I am wondering is, is it possible to take n (where n is a constant known at compile time) values from v and pass them to an arbitrary function? I know that this is doable with variadic templates:
template<typename... T>
void pass(void (*func)(int, int, int), T... t) {
func(t...);
}
And then we hope 'pass' is called with exactly 3 integers. The details don't matter so much. What I am wondering is, is the following somehow doable:
void pass(void (*func)(int, int, int), std::vector<int> &t) {
auto iter = t.begin();
func((*iter++)...);
}
Where ... is being used like a variadic template? Essentially, I'm asking if I can
Expand a std::vector or other STL container into a variadic template with n elements
And/or in-order pass these values directly to a function being called
Is this possible with C++11? Noting that I need this to work on MSVC v120/VS2013.
It's definitely possible, but you cannot determine the safety of doing it at compile time. This is, as WhozCraig says, because the vector lacks a compile-time size.
I'm still trying to earn my template meta programming wings, so I may have done things a little unusually. But the core idea here is to have a function template recursively invoke itself with the next item in the vector until it has built up a parameter pack with the desired parameters. Once it has that, it's easy to pass it to the function in question.
The implementation of the core here is in apply_first_n, which accepts a target std::function<R(Ps...)>, and a vector, and a parameter pack of Ts.... When Ts... is shorter than Ps... it builds up the pack; once it's the same size, it passes it to the function.
template <typename R, typename... Ps, typename... Ts>
typename std::enable_if<sizeof...(Ps) == sizeof...(Ts), R>::type
apply_first_n(std::function<R(Ps...)> f, const std::vector<int> &v, Ts&&... ts)
{
if (sizeof...(Ts) > v.size())
throw std::out_of_range("vector too small for function");
return f(std::forward<Ts>(ts)...);
}
template <typename R, typename... Ps, typename... Ts>
typename std::enable_if<sizeof...(Ps) != sizeof...(Ts), R>::type
apply_first_n(std::function<R(Ps...)> f, const std::vector<int> &v, Ts&&... ts)
{
const int index = sizeof...(Ps) - sizeof...(Ts) - 1;
static_assert(index >= 0, "incompatible function parameters");
return apply_first_n(f, v, *(std::begin(v) + index), std::forward<Ts>(ts)...);
}
You call this with, e.g., apply_first_n(std::function<int(int, int)>(f), v);. In the live example, make_fn just makes the conversion to std::function easier, and ProcessInts is a convenient testing function.
I'd love to figure out how to avoid the use of std::function, and to repair any other gross inefficiencies that exist. But I'd say this is proof that it's possible.
For reference, I took the above approach further, handling set, vector, tuple, and initializer_list, as well as others that match the right interfaces. Removing std::function seemed to require the func_info traits class, as well as several overloads. So while this extended live example is definitely more general, I'm not sure I'd call it better.