Related
I am attempting to employ C++17 fold expressions and the C++14 indices trick to flatten an arbitrary input consisting of tuples and non-tuples.
The expected result should at least conform to these requirements:
constexpr auto bare = 42;
constexpr auto single = std::tuple{bare};
constexpr auto nested_simple = std::tuple{single};
constexpr auto multiple = std::tuple{bare, bare};
constexpr auto nested_multiple = std::tuple{multiple};
constexpr auto multiply_nested = std::tuple{multiple, multiple};
static_assert(flatten(bare) == bare);
static_assert(flatten(single) == bare);
static_assert(flatten(nested_simple) == bare);
static_assert(flatten(multiple) == multiple);
static_assert(flatten(nested_multiple) == multiple);
static_assert(flatten(multiply_nested) == std::tuple{bare, bare, bare, bare});
I have relatively simple code to handle all but the last case:
template<typename T>
constexpr decltype(auto) flatten(T&& t)
{
return std::forward<T>(t);
}
template<typename T>
constexpr decltype(auto) flatten(std::tuple<T> t)
{
return std::get<0>(t);
}
template<typename... Ts>
constexpr decltype(auto) flatten_multi(Ts&&... ts)
{
return std::make_tuple(flatten(ts)...);
}
template<typename... Ts, std::size_t... Indices>
constexpr decltype(auto) flatten_impl(std::tuple<Ts...> ts, const std::index_sequence<Indices...>&)
{
return flatten_multi(std::get<Indices>(ts)...);
}
template<typename... Ts>
constexpr decltype(auto) flatten(std::tuple<Ts...> ts)
{
return flatten_impl(ts, std::make_index_sequence<sizeof...(Ts)>());
}
Live demo here. Obviously, it doesn't handle multiply nested items well.
The more advanced form to handle the multiply_nested case I haven't found. I tried applying operator>> to be able to use fold expressions, but haven't been able to get anything that compiles. My last attempt can be found here. The core idea is to use operator>> in a fold expression to combine elements 2 by 2, each time unwrapping the previous result.
It seems to me I should be able to use something like std::tuple_cat, but it shouted at me quite loudly for reasons I couldn't decipher completely.
So my question is this: what am I missing? How can I unwrap an arbitrarily deeply arbitrarily nested tuple-like input?
I propose to SFINAE on presence of tuple
// Simple traits
template <typename T> struct is_tuple : std::false_type{};
template <typename... Ts> struct is_tuple<std::tuple<Ts...>> : std::true_type{};
// utility to ensure return type is a tuple
template<typename T>
constexpr decltype(auto) as_tuple(T t) { return std::make_tuple(t); }
template<typename ...Ts>
constexpr decltype(auto) as_tuple(std::tuple<Ts...> t) { return t; }
// Simple case
template<typename T>
constexpr decltype(auto) flatten(T t)
{
return t;
}
// Possibly recursive tuple
template<typename T>
constexpr decltype(auto) flatten(std::tuple<T> t)
{
return flatten(std::get<0>(t));
}
// No more recursion, (sizeof...Ts != 1) with above overload
template<typename ...Ts, std::enable_if_t<!(is_tuple<Ts>::value || ...), bool> = false>
constexpr decltype(auto) flatten(std::tuple<Ts...> t)
{
return t;
}
// Handle recursion
template<typename ...Ts, std::enable_if_t<(is_tuple<Ts>::value || ...), bool> = false>
constexpr decltype(auto) flatten(std::tuple<Ts...> t)
{
return std::apply([](auto...ts)
{
return flatten(std::tuple_cat(as_tuple(flatten(ts))...));
}, t);
}
Demo
namespace flattenns {
struct flat_t {};
template<std::size_t... Is, class...As>
constexpr auto flatten( std::index_sequence<Is...>, flat_t, std::tuple<As...> as ) {
return std::tuple_cat( flatten(flat_t{}, std::get<Is>(as))... );
}
template<class...As, class...Ts>
constexpr auto flatten( flat_t, std::tuple<As...> as ) {
return flatten( std::make_index_sequence<sizeof...(As)>{}, flat_t{}, as );
}
template<class T>
constexpr std::tuple<T> flatten( flat_t, T t ) { return {t}; }
template<class...Ts>
constexpr auto flatten( flat_t, Ts... ts ) {
return std::tuple_cat( flatten(flat_t{}, ts)... );
}
constexpr std::tuple<> flatten( flat_t ) { return {}; }
}
template<class...Ts>
constexpr auto sane_flatten( Ts...ts ) {
return flattenns::flatten(flattenns::flat_t{}, ts...);
}
// to take std::tuple<int>(7) -> 7
namespace insanens {
template<class...Ts>
constexpr auto unpack_single( std::tuple<Ts...> t ) {return t;}
template<class T>
constexpr auto unpack_single( std::tuple<T> t ) {return std::get<0>(t);}
}
template<class...Ts>
constexpr auto insane_flatten( Ts...ts ) {
return insanens::unpack_single( sane_flatten(ts...) );
}
template<class...Ts>
constexpr auto flatten( Ts...ts ) {
return insane_flatten(ts...);
}
As noted above, flatten( std::tuple<int>(7) ) should NOT BE 7. That is insanity.
But as you want it, I add it as a post-processing step.
Your operation is otherwise relatively sane. You are recursively applying [[x],[y]] to [x,y]. The final unboxing is not sane. By splitting it off, the code becomes easy, which is also evidence why it is insane.
Live example.
In case you are wondering, the flat_t tag type exists in order to (a) split the index sequence from a possible argument (which could be done by having a different function name) and (b) enable ADL lookup so every implementation of flatten can see all of the other ones.
Here is another version that has two design goals:
avoid construction of temporary tuples and avoid std::tuple_cat
explicitly determine the types in the final tuple
For avoiding temporary tuples and std::tuple_cat, it is useful to predict the final size of the output tuple. Let us define a helper called get_rank:
#include <cstddef>
#include <tuple>
#include <type_traits>
template<class T>
struct Type {// tag type
using type = T;
};
template<class T>
constexpr std::size_t get_rank(Type<T>) {
static_assert(!std::is_const<T>{} && !std::is_volatile<T>{}, "avoid surprises");
return 1;
}
template<class... Ts>
constexpr std::size_t get_rank(Type< std::tuple<Ts...> >) {
return (0 + ... + get_rank(Type<Ts>{}));
}
The flatten function can utilize get_rank in order to create an index sequence for the elements of the output tuple. This sequence is passed to flatten_impl together with the forwarded input tuple and a type tag. Let us explicitly provide lvalue and rvalue overloads for the interface function, but use perfect forwarding internally:
#include <cstddef>
#include <tuple>
#include <utility>
// to be implemented
#include "tuple_element_at_rankpos_t.hpp"
#include "get_at_rankpos.hpp"
template<std::size_t... rank_positions, class Tuple, class... Ts>
constexpr auto flatten_impl(
std::index_sequence<rank_positions...>,
Tuple&& tuple,
Type< std::tuple<Ts...> > tuple_tag
) {
return std::tuple<
tuple_element_at_rankpos_t< rank_positions, std::tuple<Ts...> >...
>{
get_at_rankpos<rank_positions>(std::forward<Tuple>(tuple), tuple_tag)...
};
}
template<class... Ts>
constexpr auto flatten(const std::tuple<Ts...>& tuple) {
using TupleTag = Type< std::tuple<Ts...> >;
constexpr std::size_t rank = get_rank(TupleTag{});
return flatten_impl(
std::make_index_sequence<rank>{}, tuple, TupleTag{}
);
}
template<class... Ts>
constexpr auto flatten(std::tuple<Ts...>& tuple) {
using TupleTag = Type< std::tuple<Ts...> >;
constexpr std::size_t rank = get_rank(TupleTag{});
return flatten_impl(
std::make_index_sequence<rank>{}, tuple, TupleTag{}
);
}
template<class... Ts>
constexpr auto flatten(std::tuple<Ts...>&& tuple) {
using TupleTag = Type< std::tuple<Ts...> >;
constexpr std::size_t rank = get_rank(TupleTag{});
return flatten_impl(
std::make_index_sequence<rank>{}, std::move(tuple), TupleTag{}
);
}
At this point, we need two more building blocks:
tuple_element_at_rankpos_t (like std::tuple_element_t, but for nested tuples) and
get_at_rankpos (like std::get, but for nested tuples).
Either building block shall find the type/value of an element in the nested input tuple based on the element's position in the flattened output tuple. At each nesting level, these building blocks need to extract the index for the current nesting depth from the rankpos. This common index computation can be moved to an extract_index helper. The first building block may look like this:
#include <cassert>
#include <cstddef>
#include <array>
#include <tuple>
#include <utility>
template<class... Ts>
constexpr auto extract_index(
std::size_t rankpos, Type< std::tuple<Ts...> >
) {
static_assert(sizeof...(Ts) >= 1, "do not extract from empty tuples");
constexpr auto ranks = std::array{get_rank(Type<Ts>{})...};
std::size_t index = 0;
std::size_t nested_rankpos = rankpos;
while(nested_rankpos >= ranks[index]) {
nested_rankpos -= ranks[index++];
assert(index < sizeof...(Ts));
}
return std::pair{index, nested_rankpos};
}
////////////////////////////////////////////////////////////////////////////////
template<std::size_t rankpos, class T>
constexpr auto tuple_element_at_rankpos_tag(
Type<T> /* element_tag */
) {
static_assert(rankpos == 0);
return Type<T>{};
}
template<std::size_t rankpos, class... Ts>
constexpr auto tuple_element_at_rankpos_tag(
Type< std::tuple<Ts...> > tuple_tag
) {
// constexpr auto [index, nested_rankpos] = extract_index(rankpos, tuple_tag);
constexpr std::pair pair = extract_index(rankpos, tuple_tag);
constexpr std::size_t index = pair.first;
constexpr std::size_t nested_rankpos = pair.second;
using NestedType = std::tuple_element_t< index, std::tuple<Ts...> >;
return tuple_element_at_rankpos_tag<nested_rankpos>(
Type<NestedType>{}
);
}
template<std::size_t rankpos, class Tuple>
using tuple_element_at_rankpos_t = typename decltype(
tuple_element_at_rankpos_tag<rankpos>(Type<Tuple>{})
)::type;
The second building block is a repetition of the same glue code as above. In addition to the type we need to handle the values (lvalue, const lvalue, rvalue). Using perfect forwarding we may write:
template<std::size_t rankpos, class Element, class T>
constexpr decltype(auto) get_at_rankpos(
Element&& element,
Type<T> /* element_tag */
) {
static_assert(rankpos == 0);
return std::forward<Element>(element);
}
template<std::size_t rankpos, class Tuple, class... Ts>
constexpr decltype(auto) get_at_rankpos(
Tuple&& tuple,
Type< std::tuple<Ts...> > tuple_tag
) {
// constexpr auto [index, nested_rankpos] = extract_index(rankpos, tuple_tag);
constexpr std::pair pair = extract_index(rankpos, tuple_tag);
constexpr std::size_t index = pair.first;
constexpr std::size_t nested_rankpos = pair.second;
using NestedType = std::tuple_element_t< index, std::tuple<Ts...> >;
return get_at_rankpos<nested_rankpos>(
std::get<index>(std::forward<Tuple>(tuple)),
Type<NestedType>{}
);
}
Something perhaps a little more straightforward, although more verbose: partial class template specialization + if constexpr:
The basic approach is to specialize the following base class:
template<class... T>
struct flatten
{};
To account for our three cases:
A bare value
A tuple of one thing
A tuple of more than one thing
Case #1, the base case, is fairly straightforward, just return what we get:
//base case: something that isn't another tuple
template<class T>
struct flatten<T>
{
template<class U>
constexpr decltype(auto) operator()(U&& _value){
return std::forward<U>(_value);
}
};
Case #2 is also pretty straightforward, just recurse on itself until we reach Case #1
// recursive case 1 : plain old tuple of one item
template<class T>
struct flatten<std::tuple<T>>
{
template<class U>
constexpr decltype(auto) operator()(U&& _tup){
return flatten<std::remove_cvref_t<T>>{}(std::get<0>(_tup));
}
};
Case #3 is long because of the possible sub-cases, but each block is pretty readable. We
Flatten the first element (possibly recurses)
Flatten the rest of the elements (possible recurses)
And then we have four cases to consider:
We have two tuples (e.g., tuple<int, int>, tuple<int, int>)
We have a tuple and a value (e.g., tuple<int, int>, int)
We have a value and a tuple (e.g., int, tuple<int, int>)
We have two values (e.g., int, int)
We just need one helper function that allows us to strip the head off a tuple and return the rest of it.
// helper for getting tuple elements except the first one
template<template<class...> class Tup, class... T, size_t... indices>
constexpr auto get_rest_of_tuple(const Tup<T...>& _tup, std::index_sequence<indices...>){
return std::make_tuple(std::get<indices + 1>(_tup)...);
}
and some helper traits:
// some type traits to use for if constexpr
template<class T>
struct is_tuple : std::false_type{};
template<class... T>
struct is_tuple<std::tuple<T...>> : std::true_type{};
template<class T>
constexpr bool is_tuple_v = is_tuple<T>::value;
Finally the impl:
// recursive case 2: tuple of more than one item
template<class First, class Second, class... Rest>
struct flatten<std::tuple<First, Second, Rest...>>
{
template<class Tup>
constexpr decltype(auto) operator()(Tup&& _tup){
auto flattened_first = flatten<std::remove_cvref_t<First>>{}(std::get<0>(_tup));
auto restTuple = get_rest_of_tuple(_tup, std::make_index_sequence<sizeof...(Rest)+1>{});
auto flattened_rest = flatten<std::remove_cvref_t<decltype(restTuple)>>{}(restTuple);
// both are tuples
if constexpr(is_tuple_v<decltype(flattened_first)> && is_tuple_v<decltype(flattened_rest)>)
{
return std::tuple_cat(flattened_first, flattened_rest);
}
// only second is tuple
if constexpr(!is_tuple_v<decltype(flattened_first)> && is_tuple_v<decltype(flattened_rest)>)
{
return std::tuple_cat(std::make_tuple(flattened_first), flattened_rest);
}
//only first is tuple
if constexpr(is_tuple_v<decltype(flattened_first)> && !is_tuple_v<decltype(flattened_rest)>)
{
return std::tuple_cat(flattened_first, std::make_tuple(flattened_rest));
}
// neither are tuples
if constexpr(!is_tuple_v<decltype(flattened_first)> && !is_tuple_v<decltype(flattened_rest)>)
{
return std::tuple_cat(std::make_tuple(flattened_first), std::make_tuple(flattened_rest));
}
}
};
} // namespace detail
Finally, we use trampolining to hide all these details from the end user by shoving them into a details namespace and exposing the following function to call into them:
template<class T>
constexpr decltype(auto) flatten(T&& _value){
return detail::flatten<std::remove_cvref_t<T>>{}(std::forward<T>(_value));
}
Demo
(includes some additional tests for correctness)
While the impl of Case #3 above is pretty straightforward it is both verbose and a bit inefficient (the compiler evaluates each of those if constexpr statements when it should only evaluate one, but I didn't want to string along else branches because of the nesting).
We can pretty vastly simplify Case #3 by diverting to two helper functions that detect whether the argument is a tuple of not and return the right thing:
template<class U, std::enable_if_t<!is_tuple_v<U>, int> = 0>
constexpr decltype(auto) flatten_help(U&& _val){
return std::make_tuple(_val);
}
template<class... T>
constexpr decltype(auto) flatten_help(const std::tuple<T...>& _tup){
return _tup;
}
// recursive case 2: tuple of more than one item
template<class First, class Second, class... Rest>
struct flatten<std::tuple<First, Second, Rest...>>
{
template<class Tup>
constexpr decltype(auto) operator()(Tup&& _tup){
auto flattened_first = flatten<std::remove_cvref_t<First>>{}(std::get<0>(_tup));
auto restTuple = get_rest_of_tuple(_tup, std::make_index_sequence<sizeof...(Rest)+1>{});
auto flattened_rest = flatten<std::remove_cvref_t<decltype(restTuple)>>{}(restTuple);
return std::tuple_cat(flatten_help(flattened_first), flatten_help(flattened_rest));
}
};
Demo 2
I am writing a method to extract values from arbitrarily nested structs. I am almost there, but would like to also provide an option to convert the value retrieved (by default no conversion). Since parameter packs can't be followed by another template parameter, I have to fudge this a bit. The below works except for the indicated line:
#include <iostream>
#include <type_traits>
typedef struct {
int a;
int b;
} bar;
typedef struct {
int c;
bar d;
} baz;
template <typename T, typename S, typename... Ss>
auto inline getField(const T& obj, S field1, Ss... fields)
{
if constexpr (!sizeof...(fields))
return obj.*field1;
else
return getField(obj.*field1, fields...);
}
template <typename Obj, typename Out, class ...C, typename... T>
auto inline getFieldC(const Obj& obj, Out, T C::*... field)
{
return static_cast<Out>(getField(obj, field...));
}
template<class T> struct tag_t { using type = T; };
template<class...Ts>
using last = typename std::tuple_element_t< sizeof...(Ts) - 1, std::tuple<tag_t<Ts>...> >::type;
template <typename Obj, typename... T>
auto getMyFieldWrapper(const Obj& obj, T... field)
{
if constexpr (std::is_member_object_pointer_v<last<Obj, T...>>)
return getField(obj, field...);
else
return getFieldC(obj, last<Obj, T...>{}, field...); // <- this doesn't compile, need a way to pass all but last element of field
}
int main()
{
baz myObj;
std::cout << getMyFieldWrapper(myObj, &baz::c); // works
std::cout << getMyFieldWrapper(myObj, &baz::d, &bar::b); // works
std::cout << getMyFieldWrapper(myObj, &baz::d, &bar::b, 0.); // doesn't work
}
How do I implement the indicated line? I'm using the latest MSVC, and am happy to make full use of C++17 to keep things short and simple.
Usually more helpful to invert the flow. First, write a higher-order function that forwards an index sequence:
template <typename F, size_t... Is>
auto indices_impl(F f, std::index_sequence<Is...>) {
return f(std::integral_constant<size_t, Is>()...);
}
template <size_t N, typename F>
auto indices(F f) {
return indices_impl(f, std::make_index_sequence<N>());
}
That is just generally useful in lots of places.
In this case, we use it to write a higher-order function to drop the last element in a pack:
template <typename F, typename... Ts>
auto drop_last(F f, Ts... ts) {
return indices<sizeof...(Ts)-1>([&](auto... Is){
auto tuple = std::make_tuple(ts...);
return f(std::get<Is>(tuple)...);
});
}
And then you can use that:
return drop_last([&](auto... elems){
return getMyField(obj, last<Obj, T...>{}, elems...);
}, field...);
References omitted for brevity.
Of course, if you want to combine both and just rotate, you can do:
// Given f and some args t0, t1, ..., tn, calls f(tn, t0, t1, ..., tn-1)
template <typename F, typename... Ts>
auto rotate_right(F f, Ts... ts) {
auto tuple = std::make_tuple(ts...);
return indices<sizeof...(Ts)-1>([&](auto... Is){
return f(
std::get<sizeof...(Ts)-1>(tuple),
std::get<Is>(tuple)...);
});
}
used as:
return rotate_right([&](auto... elems){
return getMyField(obj, elems...);
}, field...);
How do I implement the indicated line?
Not sure to understand what do you want but... it seems to me that you can make it calling an intermediate function
template <std::size_t ... Is, typename ... Ts>
auto noLastArg (std::index_sequence<Is...> const &,
std::tuple<Ts...> const & tpl)
{ return getMyField(std::get<Is>(tpl)...); }
you can rewrite your function as follows
template <typename Obj, typename ... T>
auto getMyFieldWrapper (Obj const & obj, T ... field)
{
if constexpr (std::is_member_object_pointer<last<Obj, T...>>::value )
return getMyField(obj, field...);
else
return noLastArg(std::make_index_sequence<sizeof...(T)>{},
std::make_tuple(obj, field...));
}
The idea is pack the arguments for getMyField in a std::tuple of sizeof...(T)+1u elements (+1 because there is also obj) and call getMyField() unpacking the first sizeof...(T) of them.
But isn't clear, to me, if you want also last<Obj, T...>{}.
In this case, the call to noLastArg() become
return noLastArg(std::make_index_sequence<sizeof...(T)+1u>{},
std::make_tuple(obj, last<Obj, T...>{}, field...));
Context
Firstly, some context: I'm using an empty struct called nothing to emulate something similar to "regular void" in order to prettify some interfaces that rely on chaining multiple function objects together.
struct nothing { };
Example usage:
when_all([]{ return 0; }, []{ }, []{ return 'a'; })
.then([](int, char){ }); // result of lambda in the middle ignored
In the above example, what's actually happening is that I'm packaging all the results of the function objects passed to when_all in an std::tuple, converting void to nothing (in this example: std::tuple<int, nothing, char>), then I'm using a helper function called apply_ignoring_nothing that invokes a function object by unpacking an std::tuple, ignoring the elements that are nothing.
auto f_then = [](int, char){ };
auto args = std::tuple{0, nothing{}, 'a'};
apply_ignoring_nothing(f_then, args); // compiles
apply_ignoring_nothing is implemented in terms of call_ignoring_nothing.
Question
I have a function call_ignoring_nothing with the following signature:
template <typename F, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs);
This function will invoke f by perfectly-forwarding all xs... for which the compile-time is_nothing_v<T> returns false.
is_nothing_v is defined as follows:
template <typename T>
inline constexpr bool is_nothing_v = std::is_same_v<std::decay_t<T>, nothing>;
The way I implemented call_ignoring_nothing is recursively. The base case only takes f and simply invokes it:
#define FWD(x) ::std::forward<decltype(x)>(x)
template <typename F>
constexpr decltype(auto) call_ignoring_nothing(F&& f)
{
return returning_nothing_instead_of_void(FWD(f));
}
The recursive case takes f, x, and xs..., and conditionally binds x as one of f's arguments if !is_nothing_v<decltype(f)> through a lambda. It then recurses over call_ignoring_nothing passing the newly-created lambda as f:
template <typename F, typename T, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, T&& x, Ts&&... xs)
{
return call_ignoring_nothing(
[&](auto&&... ys) -> decltype(auto) {
if constexpr(is_nothing_v<T>)
{
return FWD(f)(FWD(ys)...);
}
else
{
return FWD(f)(FWD(x), FWD(ys)...);
}
},
FWD(xs)...);
}
I would like to implement call_ignoring_nothing in an iterative manner, possibly making use of pack expansion to filter out the arguments without recursion.
Is it possible to implement call_ignoring_nothing without recursion? I couldn't think of any technique that allows arguments to be filtered out during pack expansion.
Not so different from the Griwes suggestion but... I suppose you can use std::apply(), std::tuple_cat(), std::get() and tuples that are empty or with value according the value of is_nothing_v.
I mean... something like [edit: improved with a suggestion from T.C. and an example from the OP itself (Vittorio Romeo)]
template <bool B, typename ... Ts>
constexpr auto pick_if (Ts && ... xs)
{
if constexpr ( B )
return std::forward_as_tuple(std::forward<Ts>(xs)...);
else
return std::tuple{};
}
template <typename F, typename ... Ts>
constexpr decltype(auto) call_ignoring_nothing (F && f, Ts && ... xs)
{
return std::apply(f,
std::tuple_cat(pick_if<!is_nothing_v<Ts>>(std::forward<Ts>(xs))...)
);
}
The following is a working example
#include <tuple>
#include <iostream>
#include <type_traits>
struct nothing { };
template <typename T>
constexpr bool is_nothing_v = std::is_same<std::decay_t<T>, nothing>::value;
template <bool B, typename ... Ts>
constexpr auto pick_if (Ts && ... xs)
{
if constexpr ( B )
return std::forward_as_tuple(std::forward<Ts>(xs)...);
else
return std::tuple{};
}
template <typename F, typename ... Ts>
constexpr decltype(auto) call_ignoring_nothing (F && f, Ts && ... xs)
{
return std::apply(f,
std::tuple_cat(pick_if<!is_nothing_v<Ts>>(std::forward<Ts>(xs))...)
);
}
float foo (int a, float b) { return a + b; }
int main ()
{
std::cout << call_ignoring_nothing(foo, nothing{}, 12, nothing{},
2.3f, nothing{}); // print 14.3
}
live example on wandbox
Here's another take that doesn't depend on tuple_cat. First calculate the positions at which a pack of bools is true via a "normal" constexpr function template:
template<class... Bools>
constexpr int count(Bools... bs)
{
return (bool(bs) + ...);
}
template<bool... bs>
constexpr std::array<std::size_t, count(bs...)> indices()
{
std::array<std::size_t, count(bs...)> ret = {};
std::size_t i = 0, j = 0;
for(bool b : {bs...}) {
if(b) {
ret[j] = i;
++j;
}
++i;
}
return ret;
}
Then convert the result to a index_sequence:
template<bool...bs, std::size_t...Is>
constexpr auto indices_as_sequence_helper(std::index_sequence<Is...>)
{
return std::index_sequence<indices<bs...>()[Is]...>{};
}
template<bool...bs>
constexpr auto indices_as_sequence()
{
return indices_as_sequence_helper<bs...>(std::make_index_sequence<count(bs...)>());
}
Then it's a simple matter of forward_as_tuple + get with the index_sequence:
template <typename F, typename... Ts, std::size_t... Is>
constexpr decltype(auto) call_some(std::index_sequence<Is...>, F&& f, Ts&&... xs)
{
return std::forward<F>(f)(
std::get<Is>(std::forward_as_tuple(std::forward<Ts>(xs)...))...);
}
template <typename F, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs)
{
return call_some(indices_as_sequence<!is_nothing_v<Ts>...>(),
std::forward<F>(f), std::forward<Ts>(xs)...);
}
I have a family of classes with methods with the following signature:
double compute(list<T> pars)
This method performs a calculation with the parameters received through pars. For each compute(list) method, I have another compute(x1, x2, ..., xn) which is the method implementing the real calculation. Thus, compute(pars) should do some such as:
double compute(list<T> pars)
{
T x1 = list.pop_back();
T x2 = list.pop_back();
// .. so on until last parameter xn
T xn = list.pop_back();
return compute(x1, x2, .., xn); // here the real implementation is called
}
This pattern repeats many times, the only thing that could change is the size of pars list and of course the implementation of compute(x1, x1, ..).
I would like to find a way for "driying" this repetitive process; concretely, extracting the parameters in pars list and building the call to compute(x1, x2, .., xn). I have been trying without success to do some macro tricks.
My question is if it exists some way based on metaprogramming that allows me to implement compute(list<T> pars) once and simply reuse it n order to perform the call to compute(x1, x2, ..., xn)
EDIT: This is the signature of the other compute(x1, ...)
VtlQuantity compute(const VtlQuantity & x1,
const VtlQuantity & x2,
// any number of pars according the class
const VtlQuantity & xn) const
'VtlQuantityis a class representingdouble`'s, their units and other stuff.
You may do the following:
template <typename Func, typename T, std::size_t ... Is>
decltype(auto) apply(Func&& f, const std::list<T>& pars, std::index_sequence<Is...>)
{
std::vector<T> v(pars.rbegin(), pars.rend());
return std::forward<Func>(f)(v.at(Is)...);
}
template <std::size_t N, typename Func, typename T>
decltype(auto) apply(Func&& f, const std::list<T>& pars)
{
return apply(std::forward<Func>(f), pars, std::make_index_sequence<N>());
}
With usage similar to:
apply<6>(print, l);
Demo
To compute automatically the arity of the function, you may create a traits:
template <typename F> struct arity;
template <typename Ret, typename ...Args> struct arity<Ret(Args...)>
{
static constexpr std::size_t value = sizeof...(Args);
};
and then
template <typename Func, typename T>
decltype(auto) apply(Func&& f, const std::list<T>& pars)
{
constexpr std::size_t N = arity<std::remove_pointer_t<std::decay_t<Func>>>::value;
return apply(std::forward<Func>(f), pars, std::make_index_sequence<N>());
}
Demo
You have to enrich arity to support Functor (as the lambda).
This is a C++11 solution for the more general problem type of applying
a function or functor F, taking N type T parameters and returning type Ret, to the N arguments
at successive positions of some input iterator.
This gains several flexibilities over a solution parameterized by some container-of-T of the arguments:-
You can extract the arguments from an arbitrary N-sized range within a sequence.
The sequence need not be a container-of-T - though it must be a sequence of something convertible to T.
You can extract the arguments either last-to-first (as you do), or first-to-last,
from the standard container types or any that support forward and reverse iterators.
You may even apply F to arguments consumed directly from some input stream, without
intermediate extraction.
And of course you can change your mind about the type of sequence in which
to deliver arguments without having to change the functional-application solution.
Interface
template<typename Func, typename InIter, typename Stop = std::nullptr_t>
typename function_traits<typename std::decay<Func>::type>::return_type
invoke(Func && f, InIter it, Stop stop = Stop());
You can use this like:
auto result = invoke(func,iter);
to apply func to the arguments at N successive positions of the iterator
iter.
That way, you get no range-checking that N arguments are legitimately accessible
to your program at those positions. The range-checking code that you will spot
in the implementation will compile to nothing and if you trespass out of bounds
there will be UB.
If you want range checking you can instead code:
auto result = invoke(func,iter,end);
where end is an iterator of the same type as iter delimiting the end of the
available range in the usual manner. In this case an std::out_of_range will
be thrown if N exceeds the size of the range.
Implementation
#include <type_traits>
#include <functional>
#include <string>
template<typename T>
struct function_traits;
template <typename Ret, typename ArgT, typename... ArgRest>
struct function_traits<Ret(*)(ArgT, ArgRest...)>
{
static constexpr std::size_t n_args = 1 + sizeof...(ArgRest);
using first_arg_type = ArgT;
using return_type = Ret;
};
template <typename Ret, typename ArgT, typename... ArgRest>
struct function_traits<std::function<Ret(ArgT, ArgRest...)>>
{
static constexpr std::size_t n_args = 1 + sizeof...(ArgRest);
using first_arg_type = ArgT;
using return_type = Ret;
};
namespace detail {
template<typename Left, typename Right>
typename std::enable_if<!std::is_same<Left,Right>::value>::type
range_check(Left, Right, std::string const &){}
template<typename Left, typename Right>
typename std::enable_if<std::is_same<Left,Right>::value>::type
range_check(Left start, Right end, std::string const & gripe) {
if (start == end) {
throw std::out_of_range(gripe);
}
}
template<
std::size_t N, typename Func, typename InIter, typename Stop,
typename ...Ts
>
typename std::enable_if<
N == function_traits<typename std::decay<Func>::type>::n_args,
typename function_traits<typename std::decay<Func>::type>::return_type
>::type
invoke(Func && f, InIter, Stop, Ts...args)
{
return f(args...);
}
template<
std::size_t N, typename Func, typename InIter, typename Stop,
typename ...Ts
>
typename std::enable_if<
N != function_traits<typename std::decay<Func>::type>::n_args,
typename function_traits<typename std::decay<Func>::type>::return_type
>::type
invoke(Func && f, InIter it, Stop stop, Ts...args)
{
range_check(it,stop,
"Function takes more arguments than are available "
"in `" + std::string(__PRETTY_FUNCTION__) + '`');
using arg_type = typename
function_traits<typename std::decay<Func>::type>::first_arg_type;
auto arg = static_cast<arg_type>(*it);
return invoke<N + 1>(std::forward<Func>(f),++it,stop,args...,arg);
}
} // namespace detail
template<typename Func, typename InIter, typename Stop = std::nullptr_t>
typename function_traits<typename std::decay<Func>::type>::return_type
invoke(Func && f, InIter it, Stop stop = Stop())
{
return detail::invoke<0>(std::forward<Func>(f),it,stop);
}
The two specializations of function_traits<T> provided will restrict
compilation to functional types T that take at least one argument, which should
suffice for likely applications. Should you need to support
invocation on types taking 0 arguments then you can augment them with:
template <typename Ret>
struct function_traits<Ret(*)()>
{
static constexpr std::size_t n_args = 0;
using return_type = Ret;
};
template <typename Ret>
struct function_traits<std::function<Ret()>>
{
static constexpr std::size_t n_args = 0;
using return_type = Ret;
};
The specialization for free functions function_traits<Ret(*)(ArgT, ArgRest...)>,
is strictly a redundant convenience, since they too could be wrapped in std::function
objects, as you're obliged to do for anything fancier than a free function.
Demo
For a program that exercises the features discussed you can append:
#include <iostream>
#include <list>
#include <vector>
#include <deque>
#include <sstream>
#include <iterator>
struct num
{
double d;
explicit operator double() const {
return d;
}
};
double add4(double d0, double d1, double d2, double d3)
{
std::cout << d0 << '+' << d1 << '+' << d2 << '+' << d3 << "\n=";
return d0 + d1 + d2 + d3;
}
int multiply2(int i0, int i1)
{
std::cout << i0 << '*' << i1 << "\n=";
return i0 * i1;
}
struct S
{
int subtract3(int i0, int i1, int i2) const
{
std::cout << i0 << '-' << i1 << '-' << i2 << "\n=";
return i0 - i1 - i2;
}
int compute(std::list<int> const & li) const {
std::function<int(int,int,int)> bind = [this](int i0, int i1, int i2) {
return this->subtract3(i0,i1,i2);
};
return invoke(bind,li.begin());
}
};
int main()
{
std::vector<double> vd{1.0,2.0,3.0,4.0};
std::vector<double> vdshort{9.0};
std::list<int> li{5,6,7,8};
std::deque<num> dn{num{10.0},num{20.0},num{30.0},num{40.0}};
std::istringstream iss{std::string{"10 9 8"}};
std::istream_iterator<int> it(iss);
std::cout << invoke(add4,vd.rbegin()) << '\n';
std::cout << invoke(multiply2,li.begin()) << '\n';
std::cout << invoke(add4,dn.rbegin()) << '\n';
std::cout << invoke(multiply2,++it) << '\n';
S s;
std::cout << '=' << s.compute(li) << '\n';
try {
std::cout << invoke(add4,vdshort.begin(),vdshort.end()) << '\n';
} catch(std::out_of_range const & gripe) {
std::cout << "Oops :(\n" << gripe.what() << '\n';
}
return 0;
}
The case of:
S s;
std::cout << '=' << s.compute(li) << '\n';
is particularly pertinent to your particular problem, since here we call
S::compute(std::list<int> const & li) to apply another non-static method
of S to arguments delivered in the list li. See in the implementation
of S::compute how the use of a lambda can conveniently bind both the
calling S object and S::compute into an std::function we can
pass to invoke.
Live demo
C++17 solution below. wandbox link
(Greatly simplified thanks to Jarod42)
Assumes the number of arguments N is known at compile-time, but the list can have any size.
This calls pop_back() multiple times as shown in the example, then calls a function.
template <typename T>
struct list
{
T pop_back() { return T{}; }
};
namespace impl
{
template<typename TList, std::size_t... TIs>
auto list_to_tuple(TList& l, std::index_sequence<TIs...>)
{
using my_tuple = decltype(std::make_tuple((TIs, l.pop_back())...));
return my_tuple{((void)TIs, l.pop_back())...};
}
}
template<std::size_t TN, typename TList>
auto list_to_tuple(TList& l)
{
return impl::list_to_tuple(l, std::make_index_sequence<TN>());
}
template <std::size_t TN, typename TList, typename TF>
auto call_with_list(TList& l, TF&& f)
{
return std::experimental::apply(f, list_to_tuple<TN>(l));
}
void test_compute(int, int, int)
{
// ...
}
int main()
{
list<int> l{};
call_with_list<3>(l, test_compute);
}
How does it work?
The idea is that we "convert" a list to a tuple, specifying how many elements we want to pop from the list at compile-time using list_to_tuple<N>(list).
After getting a tuple from the list, we can use std::experimental::apply to call a function by applying the elements of the tuple as arguments: this is done by call_with_list<N>(list, func).
To create a tuple from the list, two things needs to be done:
Creating an std::tuple<T, T, T, T, ...>, where T is repeated N times.
Call list<T>::pop_back() N times, putting the items in the tuple.
To solve the first problem, decltype is used to get the type of the following variadic expansion: std::make_tuple((TIs, l.pop_back())...). The comma operator is used so that TIs, l.pop_back() evaluates to decltype(l.pop_back()).
To solve the second problem, a variadic expansion is used inside the std::initializer_list tuple constructor, which guarantees order-of-evaluation: return my_tuple{((void)TIs, l.pop_back())...};. The same comma operator "trick" described above is used here.
Can I write it in C++11?
Yes, but it will be slightly more "annoying".
std::experimental::apply is not available: look online for solutions like this one.
std::index_sequence is not available: you will have to implement your own.
template<class T> using void_t = void;
template<class T, class F, std::size_t N=0, class=void>
struct arity:arity<T, F, N+1> {};
template<class F, class T, class Indexes>
struct nary_result_of{};
template<std::size_t, class T>
using ith_T=T;
template<class F, class T, std::size_t...Is>
struct nary_result_of<F, T, std::index_sequence<Is...>>:
std::result_of<F( ith_T<Is, T> )>
{};
template<class T, class F, std::size_t N>
struct arity<T, F, N, void_t<
typename nary_result_of<F, T, std::make_index_sequence<N>>::type
>>:
std::integral_constant<std::size_t, N>
{};
arity uses one C++14 feature (index sequences, easy to write in C++11).
It takes types F and a T and tells you the least number of Ts you can pass to F to make the call valid. If no number of T qualify, it blows your template instantiation stack and your compiler complains or dies.
template<class T>
using strip = typename std::remove_reference<typename std::remove_cv<T>::type>::type;
namespace details {
template<class T, std::size_t N, class F, class R,
std::size_t...Is
>
auto compute( std::index_sequence<Is...>, F&& f, R&& r ) {
std::array<T, N> buff={{
(void(Is), r.pop_back())...
}};
return std::forward<F>(f)( buff[Is]... );
}
}
template<class F, class R,
class T=strip< decltype( *std::declval<R&>().begin() ) >
>
auto compute( F&& f, R&& r ) {
return details::compute( std::make_index_sequence<arity<F,T>{}>{}, std::forward<F>(f), std::forward<R>(r) );
}
The only thing really annoying to convert to C++11 is the auto return type on compute. I'd have to rewrite my arity.
This version should auto detect the arity of even non-function pointers, letting you call this with lambdas or std::functions or what have you.
I'm trying to make this program compile properly:
#include <vector>
#include <iostream>
int f(int a, int b)
{
::std::cout << "f(" << a << ", " << b << ") == " << (a + b) << '\n';
return a + b;
}
template <typename R, typename V>
R bind_vec(R (*f)(), const V &vec, int idx=0)
{
return f();
}
template <typename R, typename V, typename Arg1, typename... ArgT>
R bind_vec(R (*f)(Arg1, ArgT...), const V &vec, int idx=0)
{
const Arg1 &arg = vec[idx];
auto call = [arg, f](ArgT... args) -> R {
return (*f)(arg, args...);
};
return bind_vec(call, vec, idx+1);
}
int foo()
{
::std::vector<int> x = {1, 2};
return bind_vec(f, x);
}
Ideally I'd like bind_vec to take an arbitrary functor as an argument instead of just a function pointer. The idea is to pull the function arguments from a ::std::vector at compile time.
This isn't the final use for this, but it's a stepping stone to where I want to go. What I'm really doing is generating wrapper functions that unwrap their arguments from promises in a future/promise type system at compile time. These wrapper functions will themselves be promises.
In my ultimate use-case I can count on the functors being ::std::functions. But it would be nice to have an idea of how it should work for more general functors as well since I think this is a broadly interesting problem.
OK, first off, detecting the arity of a functor can be done, but it's a bit involved and best left to a separate question. Let's assume you will specify the arity of the functor in the call. Similarly, there are ways to obtain the return type of a callable object, but that's also beyond the scope of this question. Let's just assume the return type is void for now.
So we want to say,
call(F f, C v);
and that should say f(v[0], v[1], ..., v[n-1]), where f has arity n.
Here's an approach:
template <unsigned int N, typename Functor, typename Container>
void call(Functor const & f, Container const & c)
{
call_helper<N == 0, Functor, Container, N>::engage(f, c);
}
We need the helper:
#include <functional>
#include <cassert>
template <bool Done, typename Functor, typename Container,
unsigned int N, unsigned int ...I>
struct call_helper
{
static void engage(Functor const & f, Container const & c)
{
call_helper<sizeof...(I) + 1 == N, Functor, Container,
N, I..., sizeof...(I)>::engage(f, c);
}
};
template <typename Functor, typename Container,
unsigned int N, unsigned int ...I>
struct call_helper<true, Functor, Container, N, I...>
{
static void engage(Functor const & f, Container const & c)
{
assert(c.size() >= N);
f(c[I]...);
}
};
Example:
#include <vector>
#include <iostream>
void f(int a, int b) { std::cout << "You said: " << a << ", " << b << "\n"; }
struct Func
{
void operator()(int a, int b) const
{ std::cout << "Functor: " << a << "::" << b << "\n"; }
};
int main()
{
std::vector<int> v { 20, 30 };
call<2>(f, v);
call<2>(Func(), v);
}
Notes: In a more advanced version, I would deduce the arity of the callable object with some more template machinery, and I would also deduce the return type. For this to work, you'll need several specializations for free functions and various CV-qualified class member functions, though, and so this would be getting too large for this question.
Something like this is easily possible for (member) function pointers, but for functors with potentially overloaded operator(), this gets a dang lot harder. If we assume that you have a way to tell how many arguments a function takes (and assume that the container actually has that many elements), you can just use the indices trick to expand the vector into an argument list, for example with std::next and a begin() iterator:
#include <utility>
#include <iterator>
template<class F, class Args, unsigned... Is>
auto invoke(F&& f, Args& cont, seq<Is...>)
-> decltype(std::forward<F>(f)(*std::next(cont.begin(), Is)...))
{
return std::forward<F>(f)(*std::next(cont.begin(), Is)...);
}
template<unsigned ArgC, class F, class Args>
auto invoke(F&& f, Args& cont)
-> decltype(invoke(std::forward<F>(f), cont, gen_seq<ArgC>{}))
{
return invoke(std::forward<F>(f), cont, gen_seq<ArgC>{});
}
This implementation works really nice for random-access containers, but not so well for forward and especially input ones. To make those work in a performant fashion, you might try to go the route of incrementing the iterator with every expanded step, but you'll run into a problem: Evaluation order of arguments to a function is unspecified, so you'll very likely pass the arguments in the wrong order.
Luckily, there is a way to force evaluation left-to-right: The list-initialization syntax. Now we just need a context where that can be used to pass arguments, and a possible one would be to construct an object, pass the function and the arguments through the constructor, and call the function in there. However, you lose the ability to retrieve the returned value, since constructors can't return a value.
Something I thought of is to create an array of iterators, which point to the correct element, and expanding those again in a second step where they are dereferenced.
#include <utility>
template<class T> using Alias = T; // for temporary arrays
template<class F, class It, unsigned N, unsigned... Is>
auto invoke_2(F&& f, It (&&args)[N], seq<Is...>)
-> decltype(std::forward<F>(f)(*args[Is]...))
{
return std::forward<F>(f)(*args[Is]...);
}
template<class F, class Args, unsigned... Is>
auto invoke_1(F&& f, Args& cont, seq<Is...> s)
-> decltype(invoke_2(std::forward<F>(f), std::declval<decltype(cont.begin())[sizeof...(Is)]>(), s))
{
auto it = cont.begin();
return invoke_2(std::forward<F>(f), Alias<decltype(it)[]>{(void(Is), ++it)...}, s);
}
template<unsigned ArgC, class F, class Args>
auto invoke(F&& f, Args& cont)
-> decltype(invoke_1(std::forward<F>(f), cont, gen_seq<ArgC>{}))
{
return invoke_1(std::forward<F>(f), cont, gen_seq<ArgC>{});
}
The code was tested against GCC 4.7.2 and works as advertised.
Since you said that the functors you are getting passed are std::functions, getting the number of arguments they take is really easy:
template<class F> struct function_arity;
// if you have the 'Signature' of a 'std::function' handy
template<class R, class... Args>
struct function_arity<R(Args...)>
: std::integral_constant<std::size_t, sizeof...(Args)>{};
// if you only have the 'std::function' available
template<class R, class... Args>
struct function_arity<std::function<R(Args...)>>
: function_arity<R(Args...)>{};
Note that you don't even need function_arity to make invoke from above work for std::function:
template<class R, class... Ts, class Args>
R invoke(std::function<R(Ts...)> const& f, Args& cont){
return invoke_1(f, cont, gen_seq<sizeof...(Ts)>{})
}
I managed to do what you want. It's simplest to explain if I leave it as not deducing the correct return type at first, I'll show how to add that later on:
#include <vector>
#include <type_traits>
namespace {
int f(int a, int b) { return 0; }
}
template <typename ...Args>
constexpr unsigned nb_args(int (*)(Args...)) {
return sizeof...(Args);
}
template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V&, Args&& ...args)
-> typename std::enable_if<sizeof...(Args) == nb_args(F()),void>::type
{
f(std::forward<Args>(args)...);
}
template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V& v, Args&& ...args)
-> typename std::enable_if<sizeof...(Args) < nb_args(F()),void>::type
{
bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args)));
}
int main() {
bind_vec(&f, std::vector<int>(), 1);
return 0;
}
There are two versions of this bind_vec - one is enabled if the parameter pack is the right size for the function. The other is enabled if it is still too small. The first version simply dispatches the call using the parameter pack, whilst the second version gets the next element (as determined by the size of the parameter pack) and recurses.
There SFINAE is done on the return type of the function in order that it not interfer with the deduction of the types, but this means it needs to be done after the function since it needs to know about F. There's a helper function that finds the number of arguments needed to call a function pointer.
To deduce the return types also we can use decltype with the function pointer:
#include <vector>
#include <type_traits>
namespace {
int f(int a, int b) { return 0; }
}
template <typename ...Args>
constexpr unsigned nb_args(int (*)(Args...)) {
return sizeof...(Args);
}
template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V&, Args&& ...args)
-> typename std::enable_if<sizeof...(Args) == nb_args(F()),decltype(f(std::forward<Args>(args)...))>::type
{
return f(std::forward<Args>(args)...);
}
template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V& v, Args&& ...args)
-> typename std::enable_if<sizeof...(Args) < nb_args(F()),decltype(bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args))))>::type
{
return bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args)));
}
int main() {
bind_vec(&f, std::vector<int>(), 1);
return 0;
}