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
Related
I have written a constexpr function which calculate the sum of the sizes of the elements of a tuple.
When called directly, the function call compiles with both tuple of values, and tuple of references.
When called through a templated function, it still compiles with tuple of values, but fail with tuple of references.
I can work around my problem using tuple of pointers instead of tuple of reference, but the API of the thing I write (a templated set of functions to ease writing SPI and I²C driver for microcontrollers) will be less clean.
Thanks for any help.
Ps: I am using gcc8.2 using c++17 standard.
Alexandre
#include <tuple>
template <typename> struct is_tuple_t: std::false_type {};
template <typename ...T> struct is_tuple_t<std::tuple<T...>> : std::true_type {};
template<typename Type>
constexpr bool is_tuple(const Type&) {
if constexpr (is_tuple_t<Type>::value)
return true;
else
return false;
}
template<class F, class...Ts, std::size_t...Is>
constexpr void for_each_in_tuple(const std::tuple<Ts...> & tupl, F func,
std::index_sequence<Is...>){
using expander = int[];
(void)expander { 0, ((void)func(std::get<Is>(tupl)), 0)... };
}
template<class F, class...Ts>
constexpr void for_each_in_tuple(const std::tuple<Ts...> & tupl, F func){
for_each_in_tuple(tupl, func, std::make_index_sequence<sizeof...(Ts)>());
}
template <typename T>
constexpr size_t size_of_tuple(const T &tup) {
static_assert(is_tuple(tup) == true, "error size_of_tuple argument must be a tuple");
size_t s=0;
for_each_in_tuple(tup, [&s](auto &&x) {
s += sizeof(x);
});
return s;
}
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
constexpr size_t st = size_of_tuple(tup);
return st;
}
int main()
{
uint16_t var;
constexpr size_t s1 = size_of_tuple(std::make_tuple(1)) ; // OK
constexpr size_t s2 = size_of_tuple(std::forward_as_tuple(var)) ; // OK
constexpr size_t f1 = foo(std::make_tuple(1)) ; // OK
constexpr size_t f2 = foo(std::forward_as_tuple(var)) ; // FAIL
}
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
constexpr size_t st = size_of_tuple(tup);
return st;
}
In this function, tup is not a constant expression, so it can't be used in the initializer of a constexpr variable.
[expr.const]¶2
An expression e is a core constant expression unless the evaluation of e [...] would evaluate one of the following expressions:
[...]
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e
Either one of these should work instead:
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
size_t st = size_of_tuple(tup);
return st;
}
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
return size_of_tuple(tup);
}
Note that Clang still rejects your code, for other violations of the same rule.
Ultimately, your size_of_tuple and is_tuple are both flawed, as they can't be used in constant expressions if their argument is a reference. If you want to use this function-like syntax, you need something like type_c from Boost.Hana:
template <typename T>
class type {};
template <typename T>
constexpr type<T> type_c{};
template <typename T>
constexpr bool is_tuple(type<T>) {
return is_tuple_t<T>::value;
}
template <typename T>
constexpr size_t size_of_tuple(type<T> tup) {
static_assert(is_tuple(tup), "size_of_tuple argument must be a tuple");
//...
}
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
size_t st = size_of_tuple(type_c<std::remove_cvref_t<decltype(tup)>>);
return st;
}
uint16_t var = 42;
constexpr size_t f2 = foo(std::forward_as_tuple(var));
The problems of your original approach are properly explained in Oktalist's answer.
Note that size_of_tuple can be implemented in one line using C++17's fold expressions:
template<class... Ts>
constexpr std::size_t size_of_tuple(const std::tuple<Ts...>&) {
return (0 + ... + sizeof(Ts));
}
However, that function is hard to use because of the argument of type const std::tuple<Ts...>&. Thus it may be desirable to introduce an empty tag type that can be "passed" to these kind of metafunctions. This idea is explained in Boost.Hana's chapter on type computations.
The following is a complete example.
static_assert(__cplusplus >= 201703L, "C++17 required");
#include <cstddef>
#include <cstdint>
#include <tuple>
// the empty tag type
template<class T>
struct Type {};
////////////////////////////////////////////////////////////////////////////////
template<class... Ts>
constexpr std::size_t size_of_tuple(Type< std::tuple<Ts...> >) {
return (0 + ... + sizeof(Ts));
}
static_assert(0 == size_of_tuple(Type< std::tuple<> >{}));
static_assert(12 == size_of_tuple(Type< std::tuple<int32_t, int64_t> >{}));
static_assert(12 == size_of_tuple(Type< std::tuple<int32_t&, int64_t&> >{}));
static_assert(2 == size_of_tuple(Type< std::tuple<char&, char> >{}));
static_assert(6 == size_of_tuple(Type< std::tuple<int32_t&, char&, char> >{}));
// fails to compile (for good reasons):
//static_assert(8 == size_of_tuple(Type< std::tuple<int32_t, uint64_t> >{}));
Furthermore, you can consider to use std::integral_constant as return type. That may become handy when you want to pass the size into another function and use it as a constexpr value. Note that std::integral_constants are implicitly convertible to their ::type. The implicit conversion is one reason why Boost.Hana introduces its own "integral-constant type". Some interesting examples can be found in Boost.Hana's chapter on Compile-time numbers. Anyway, here is the simple example with the implicitly convertible std::integral_constant:
#include <cstddef>
#include <tuple>
#include <type_traits>
template<class... Ts>
constexpr auto better_size_of_tuple(Type< std::tuple<Ts...> >) {
constexpr std::size_t ret = (0 + ... + sizeof(Ts));
return std::integral_constant<std::size_t, ret>{};
}
so here is a rewrite of the function which compile, at least with gcc 8.2 :
template
<std::size_t position, class T>
struct SizeOfTupImpl{
static constexpr size_t
size() {
if constexpr (position != 0) {
return sizeof(std::tuple_element_t<position, T>) +
SizeOfTupImpl<position - 1, T>::size();
} else {
return sizeof(std::tuple_element_t<0, T>);
}
}
};
template<class T>
constexpr size_t
size_of_tuple(const T& tup) {
static_assert(is_tuple(tup) == true, "error size_of_tuple argument must be a tuple");
constexpr std::size_t dimension = std::tuple_size<T>::value;
return SizeOfTupImpl<dimension - 1, T>::size();
}
Since i am an absolute template metaprogramming beginner, feel free to point me toward a more elegant solution !
Alexandre
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...));
I am attempting to map function f over tuples t0, t1, etc. to return the tuple
std::tuple<f(std::get<0>(t0),std:get<0>(t1),...),f(std::get<1>(t0),std::get<1>(t1),...),...). I have a version working using car,cdr, and cons but am trying to get a version working using std::index_sequence.
The code:
// Helper
template<typename T>
using make_tuple_index = std::make_index_sequence<std::tuple_size<T>::value>;
// Implementation
template<typename F, typename... Ts, std::size_t... Is>
auto mapx_n_impl(const F& f, std::index_sequence<Is...>, const Ts&... t)
{ return std::make_tuple(f(std::get<Is>(t...))...); }
// Interface
template<typename T,
typename F,
typename Indices = make_tuple_index<T>>
auto map(const T& t, const F& f)
{ return mapx_impl(t, f, Indices{}); }
// Test
auto tup1 = std::make_tuple(1.0, 2.0, 3.0);
auto tup2 = std::make_tuple(0.0, 1.0, 2.0);
auto r = mapx_n([](auto x, auto y) { return x - y; }, tup1, tup2);
The problem is expanding the parameter packs in the implementation return statement. I need it to expand t in the "inner" loop and Is in the "outer" loop. How is the expansion controlled? And, how do I fix my return statement?
UPDATE:
Based on the response from #Yakk and the further elucidation by #max66, I have simplified my code as much as I think possible. The current version integrates a version of the parameter pack expansion helper from #Yakk's answer as well as factoring out the get_element call into a lambda.
// invoke_with_pack
template<std::size_t... Is, typename F>
auto invoke_with_pack(std::index_sequence<Is...>, F&& function)
{ return function(std::integral_constant<std::size_t, Is>{}...); }
// nth
template<natural N, typename... Ts>
using nth = typename std::tuple_element<N, std::tuple<Ts...>>::type;
// make_tuple_index -- Helper template for computing indices
// corresponding to a tuple.
template<typename T>
using make_tuple_index = std::make_index_sequence<std::tuple_size<T>::value>;
// map_n -- Map <function> over <tuples> t0,t1,...
template<typename F,
typename... Ts,
typename Indices = make_tuple_index<nth<0,Ts...>>>
auto map_n(F&& function, Ts&... tuples)
{
auto get_element = [&](auto I) { return function(std::get<I>(tuples)...); };
return invoke_with_pack(Indices{}, [&](auto... Is) {
return std::make_tuple(get_element(Is)...);
});
}
Now on to figuring out how to implement fold_left and fold_right with indexes instead of car,cdr and cons.
Start with this:
namespace utility {
template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f)->decltype(auto) {
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
return index_over( std::make_index_sequence<N>{} );
}
}
that lets us avoid having to write a whole pile of functions just to expand some parameter packs. index_upto<7>()([](auto...Is){ /* here */ }) gives you a context where you have a bunch of compile time integral constants 0 through 6 in a pack.
template<class F, class T0, class...Tuples>
auto map_over_tuples( F&& f, T0&... t0, Tuples&&... tuples ) {
using tuple_size = typename std::tuple_size< std::decay_t<T0> >::type;
auto get_element = [&](auto I){
return f(std::get<I>(std::forward<T0>(t0)), std::get<I>(std::forward<Tuples>(tuples)...));
};
return index_upto<tuple_size{}>()([&](auto...Is){
return std::make_tuple( get_element(Is)... );
});
}
in some compilers, use of I has to be replaced with decltype(I)::value in get_element.
The problem is expanding the parameter packs in the implementation return statement. I need it to expand t in the "inner" loop and Is in the "outer" loop. How is the expansion controlled? And, how do I fix my return statement?
I don't see a simple and elegant way to do this.
It seems to me that you have to decouple the two packs in same way and expand first one then another.
If you see the Yakk solution, you see the inner expansion (t...) through a lambda function with single calling f() in it.
The following is a solution, based on the same principle with a template function, and the use of std::apply to leave the call of f() outside.
Frankly, I think the Yakk solution is more efficient (no need of unuseful tuples creation) so take this example as an oddity
#include <tuple>
#include <iostream>
template <std::size_t I, typename ... Ts>
auto getN (Ts const & ... t)
{ return std::make_tuple(std::get<I>(t)...); }
template<typename F, typename... Ts, std::size_t... Is>
auto mapx_n_impl(const F& f, std::index_sequence<Is...>, const Ts&... t)
{ return std::make_tuple(std::apply(f, getN<Is>(t...))...); }
template <typename F, typename T0, typename ... Ts>
auto mapx_n (F const & f, T0 const & t0, Ts const & ... ts)
{ return mapx_n_impl(f,
std::make_index_sequence<std::tuple_size<T0>::value> {}, t0, ts...); }
int main ()
{
// Test
auto tup1 = std::make_tuple(1.0, 2.0, 3.0);
auto tup2 = std::make_tuple(0.0, 1.0, 2.0);
auto r = mapx_n([](auto x, auto y) { return x - y; }, tup1, tup2);
std::cout << std::get<0U>(r) << std::endl;
std::cout << std::get<1U>(r) << std::endl;
std::cout << std::get<2U>(r) << std::endl;
}
Based on the great solutions I've developed a more general function to transform and fold (reduce) tuples. As you were mentioning fold_left and fold_right in your question, this might be of interest for the discussion.
The basic idea is to apply a second functor to the mapped (a.k.a. transformed) tuple rather than calling std::make_tuple as you did in your solution. This allows many algorithms (e.g. count_if, all_of, any_of etc.) to be implemented easily.
Live example here.
#include <tuple>
#include <functional>
#define FWD(x) std::forward<decltype(x)>(x)
namespace tuple_utils {
template<class UnaryFunc, std::size_t... Idx>
constexpr auto apply_for_each_index(std::index_sequence<Idx...>, UnaryFunc&& f) {
return FWD(f)(std::integral_constant<std::size_t, Idx>{}...);
}
template<typename T>
using make_tuple_index = std::make_index_sequence<std::tuple_size<std::decay_t<T>>::value>;
template<class... Ts>
using first_element_t = typename std::tuple_element<0, std::tuple<Ts...>>::type;
template<class T>
constexpr size_t tuple_size_v = std::tuple_size_v<std::decay_t<T>>;
template<class Map, class Reduce, class... Tuples>
constexpr auto
transform_reduce(Map &&transform_func, Reduce &&reduce_func, Tuples&&... tuples) {
using first_tuple_t = first_element_t<Tuples...>;
constexpr size_t first_tuple_size = tuple_size_v<first_tuple_t>;
static_assert(((tuple_size_v<Tuples> == first_tuple_size) && ...), "all tuples must be of same size!");
auto transform_elements_at = [&](auto Idx){
return FWD(transform_func)(std::get<Idx>(FWD(tuples))...);
};
using Indices = make_tuple_index<first_tuple_t>;
return apply_for_each_index(
Indices{},
[&](auto... Indices) {
return FWD(reduce_func)(transform_elements_at(Indices)...);
}
);
}
}
int main()
{
using tuple_utils::transform_reduce;
auto make_tuple = [](auto&&... xs) { return std::make_tuple(FWD(xs)...); };
auto equal = [](auto&& first, auto&&... rest){return ((FWD(first) == FWD(rest)) && ... ); };
constexpr auto all = [](auto... bs) { return (bs && ...);};
constexpr auto any = [](auto... bs) { return (bs || ...);};
constexpr auto count = [](auto... bs) { return (bs + ...); };
static_assert(transform_reduce(std::equal_to<>(), make_tuple, std::tuple{1,2,3,4}, std::tuple{1,2,7,8}) == std::tuple{true, true, false, false});
static_assert(transform_reduce(equal, all, std::tuple{1,2,3,4}, std::tuple{1,2,7,8}) == false);
static_assert(transform_reduce(equal, all, std::tuple{1,2,3,4}, std::tuple{1,2,3,4}, std::tuple{1,2,3,4}) == true);
static_assert(transform_reduce(equal, any, std::tuple{1,2,3,4}, std::tuple{1,2,7,8}) == true);
static_assert(transform_reduce(equal, count, std::tuple{1,2,3,4}, std::tuple{1,2,7,8}) == 2);
}
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 variadic template function foo():
template <typename... Args>
void foo(Args &&... args);
This function is intended to be invoked with all arguments of size_t. I can enforce that using some metaprogramming. I need to take the resulting list of arguments two at a time and put them into a container of std::pair<size_t, size_t>. Conceptually, something like:
std::vector<std::pair<size_t, size_t> > = {
std::make_pair(args[0], args[1]),
std::make_pair(args[2], args[3]), ...
};
Is there a straightforward way to do this? I know that by pack expansion, I could put the arguments into a flat container, but is there a way to group them two by two into std::pair objects at the same time?
Indexing into packs isn't really doable (yet?), but indexing into tuples is. Just stick everything into a tuple first, and then pull everything back out as you go. Since everything's a size_t, we can just copy:
template <size_t... Is, class Tuple>
std::vector<std::pair<size_t, size_t>>
foo_impl(std::index_sequence<Is...>, Tuple tuple) {
return std::vector<std::pair<size_t, size_t> >{
std::make_pair(std::get<2*Is>(tuple), std::get<2*Is+1>(tuple))...
};
}
template <typename... Args>
void foo(Args... args)
{
auto vs = foo_impl(std::make_index_sequence<sizeof...(Args)/2>{},
std::make_tuple(args...));
// ...
}
Suppose you are allowed to refactor your logic into an internal helper function:
template <typename ...Args>
void foo(Args &&... args)
{
foo_impl(std::make_index_sequence<sizeof...(Args) / 2>(),
std::forward<Args>(args)...);
}
Now we can operate on the argument pack index by index:
template <std::size_t ...I, typename ...Args>
void foo_impl(std::index_sequence<I...>, Args &&... args)
{
std::vector<std::pair<std::size_t, std::size_t>> v =
{ GetPair(std::integral_constant<std::size_t, I>(), args...)... };
}
It remains to implement the pair extractor:
template <typename A, typename B, typename ...Tail>
std::pair<std::size_t, std::size_t> GetPair(std::integral_constant<std::size_t, 0>,
A a, B b, Tail ... tail)
{
return { a, b };
}
template <std::size_t I, typename A, typename B, typename ...Tail>
std::pair<std::size_t, std::size_t> GetPair(std::integral_constant<std::size_t, I>,
A a, B b, Tail ... tail)
{
return GetPair<I - 1>(tail...);
}
With range-v3, you may do
template <typename... Args>
void foo(Args&&... args)
{
std::initializer_list<std::size_t> nbs = {static_cast<std::size_t>(args)...};
const auto pair_view =
ranges::view::zip(nbs | ranges::view::stride(2),
nbs | ranges::view::drop(1) | ranges::view::stride(2));
// And possibly
std::vector<std::pair<std::size_t, std::size_t>> pairs = pair_view;
// ...
}
Demo
Someone (cough #Barry cough) said that indexing into packs isn't possible.
This is C++. Impossible means we just haven't written it yet.
template<std::size_t I> struct index_t:std::integral_constant<std::size_t, I> {
using std::integral_constant<std::size_t, I>::integral_constant;
template<std::size_t J>
constexpr index_t<I+J> operator+( index_t<J> ) const { return {}; }
template<std::size_t J>
constexpr index_t<I-J> operator-( index_t<J> ) const { return {}; }
template<std::size_t J>
constexpr index_t<I*J> operator*( index_t<J> ) const { return {}; }
template<std::size_t J>
constexpr index_t<I/J> operator/( index_t<J> ) const { return {}; }
};
template<std::size_t I>
constexpr index_t<I> index{};
template<std::size_t B>
constexpr index_t<1> exponent( index_t<B>, index_t<0> ) { return {}; }
template<std::size_t B, std::size_t E>
constexpr auto exponent( index_t<B>, index_t<E> ) {
return index<B> * exponent( index<B>, index<E-1> );
}
template<std::size_t N>
constexpr index_t<0> from_base(index_t<N>) { return {}; }
template<std::size_t N, std::size_t c>
constexpr index_t<c-'0'> from_base(index_t<N>, index_t<c>) { return {}; }
template<std::size_t N, std::size_t c0, std::size_t...cs>
constexpr auto from_base(index_t<N>, index_t<c0>, index_t<cs>...) {
return
from_base(index<N>, index<c0>) * exponent(index<N>, index<sizeof...(cs)>)
+ from_base(index<N>, index<cs>...)
;
}
template<char...cs>
constexpr auto operator""_idx(){
return from_base(index<10>, index<cs>...);
}
auto nth = [](auto index_in){
return [](auto&&...elems)->decltype(auto){
using std::get;
constexpr auto I= index<decltype(index_in){}>;
return get<I>(std::forward_as_tuple(decltype(elems)(elems)...));
};
};
Now we get:
using pair_vec = std::vector<std::pair<std::size_t, std::size_t>>;
template <typename... Args>
pair_vec foo(Args &&... args) {
return
index_over< sizeof...(args)/2 >()
([&](auto...Is)->pair_vec{
return {
{
nth( Is*2_idx )( decltype(args)(args)... ),
nth( Is*2_idx+1_idx )( decltype(args)(args)... )
}...
};
});
}
where we "directly" index into our parameter packs using compile time constant indexes.
live example.