Related
I have a contrived example of what I would like to achieve.
#include <tuple>
#include <array>
template <int Val>
struct Value
{
static constexpr auto aValue = Val;
double arbitrary_operation() { return aValue * 2.0; };
};
struct Meta
{
auto do_calculation(std::floating_point auto x) { return x * 4.0; }
};
constexpr auto Values = std::tuple{Value<5>{}, Value<6>{}, Value<7>{}};
constexpr auto ValueMetadata = std::array<std::pair<int, Meta>, 3>{{{5, Meta{}}, {6, Meta{}}, {7, Meta{}}}};
void do_work()
{
std::array<double, ValueMetadata.size()> results;
for (auto& m : ValueMetadata)
{
// find first element in Values tuple that corresponds to parameterized integer (m.first == aValue)
auto value = get_first_element_in_values_tuple<m.first>(); // TODO
// perform arbitrary_operation on that Value and aggregate results
auto arbitrary_value = value.arbitrary_operation();
auto result = m.second.do_calculation(arbitrary_value);
// store in results array
}
}
The body of do_work is presenting some difficult - and I'm wondering where this can be performed at compile-time.
So I'm not sure what the templated Value class brings you. Why not make them non-template, then you can just make Values an std::array, and use constexpr algorithms from C++20?
Either way, if you insist on the tuple solution, here's one way you can create a tuple_find_if function with the help of C++20 constexpr algorithms and std::index_sequence:
template<typename Tuple, typename Func, std::size_t ... N>
consteval auto tuple_find_if_impl(Tuple tup, Func func, std::index_sequence<N...>)
{
constexpr auto compares = std::array{func(std::get<N>(tup))...};
constexpr auto index = std::ranges::find(compares, true) - compares.begin();
return std::get<index>(tup);
}
template<typename ... Ts, typename Func>
consteval auto tuple_find_if(std::tuple<Ts...> tup, Func func)
{
return tuple_find_if_impl(tup, func, std::make_index_sequence<sizeof...(Ts)>{});
}
Demo: https://godbolt.org/z/cEfPoPbdc
And here's a recursive version that works with C++17:
template<typename T, typename ... Ts, typename Func, std::size_t ... N>
constexpr auto tuple_find_if_impl(std::tuple<T, Ts...> tup, Func func, std::index_sequence<N...>)
{
if constexpr(func(std::get<0>(tup)))
{
return std::get<0>(tup);
}
else
{
return tuple_find_if_impl(std::make_tuple(std::get<N + 1>(tup)...), func, std::make_index_sequence<sizeof...(Ts) - 1>{});
}
}
template<typename ... Ts, typename Func>
constexpr auto tuple_find_if(std::tuple<Ts...> tup, Func func)
{
return tuple_find_if_impl(tup, func, std::make_index_sequence<sizeof...(Ts) - 1>{});
}
Demo: https://godbolt.org/z/sc5Txronh
You can first convert the tuple to an array and use std::find to find the appropriate index, then use variant to type-erasure the index.
#include <algorithm>
#include <variant>
template<std::size_t N>
constexpr auto VarIndexArray = []<std::size_t... Is>(std::index_sequence<Is...>) {
using VarType = std::variant<std::integral_constant<std::size_t, Is>...>;
return std::array{VarType{std::in_place_index<Is>}...};
}(std::make_index_sequence<N>{});
void do_work() {
std::array<double, ValueMetadata.size()> results;
constexpr auto ValueArray = std::apply(
[](auto... vals) { return std::array{decltype(vals)::aValue...}; }, Values);
constexpr auto IndexArray = VarIndexArray<std::tuple_size_v<decltype(Values)>>;
for (auto& m : ValueMetadata) {
// find first element in Values tuple that corresponds to parameterized integer
auto index = std::ranges::find(ValueArray, m.first) - ValueArray.begin();
auto result = std::visit(
[&m](auto index) {
auto arbitrary_value = std::get<index>(Values).arbitrary_operation();
auto result = m.second.do_calculation(arbitrary_value);
return result;
}, IndexArray[index]);
}
}
Demo
I may be misinterpreting your constraints, but if I understand correctly, you want to find the matching tuple element for each pair in your array, then aggregate the results. Depending on exactly what you want there's certainly better solutions using fold expressions and index sequences, but I believe I have something that works and produces a compile time result:
live example
template <int I, std::size_t TupleIndex>
consteval double find_match() {
// Compare I to the current aValue, if they match, return the val.
if constexpr (I == std::get<TupleIndex>(Values).aValue) {
return std::get<TupleIndex>(Values).val();
} else {
// Otherwise, move onto the next element of the tuple.
return find_match<I, TupleIndex+1>();
}
}
template <std::size_t I>
consteval double accumulate_matches() {
if constexpr (I == ValueMetadata.size()) {
return 0;
} else {
// Change this expression to accumulate with something
// other than a simple summation.
// 0 to start searching at the beginning of the tuple.
// +1 in the recursive call to get to the next element of ValueMetadata
return find_match<ValueMetadata[I].first, 0>()
+ accumulate_matches<I+1>();
}
}
consteval double get_total() {
return accumulate_matches<0>();
}
I'm trying to create a Tuple class with an arbitrary number of entries with arbitrary types using variadic templates and the ability to get the nth entry with a templatized entry method, so I can use it as follows:
Tuple<int, int, std::string, double> t(1, 2, "Hello World", 3.4);
std::cout << t.entry<1>() << std::endl; // Prints 2
std::cout << t.entry<2>() << std::endl; // Prints "Hello World"
My current approach:
template<typename ...Types>
struct Tuple;
template<typename Type>
struct Tuple<Type>
{
Tuple(Type value) : value(value) { };
Type value;
template<int Index>
Type& entry()
{
return value;
}
};
template<typename Type, typename... Types>
struct Tuple<Type, Types...> : public Tuple<Types...>
{
Tuple(Type value, Types ...args) : Tuple<Types...>(args...), value(value) { }
Type value;
template<int Index>
auto entry() -> decltype(Tuple<Types...>::entry<Index-1>())&
{
return Tuple<Types...>::entry<Index-1>();
}
template<>
Type& entry<0>()
{
return value;
}
};
The first struct providing the "base" case for one element and the second struct building recursively upon that. However, I get the error
In member function ‘decltype (((Tuple<Types ...>::entry < (Index - 1)) > <expression error>))& Tuple<Type, Types ...>::entry()’:
error: expected primary-expression before ‘)’ token
How can I call a templated method of a templated base class?
Assuming you're using at least C++14, you can use auto& instead of a trailing return type, and as pointed out by max66 you need to add the template keyword before the member function invocation in your syntax.
You can also simplify your definition because you just need an empty base class, not a class which implements a specialization for one type; your second class already implements the necessary behavior for one type. This simplification requires you to rewrite entry() using std::enable_if, or if constexpr if you're using C++17
// only needed for C++14 when using std::enable_if
#include <type_traits>
template<typename...>
struct Tuple
{
};
template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
Tuple(T value, Ts ...args) : value(value), Tuple<Ts...>(args...) { }
T value;
template<std::size_t I, std::enable_if_t<I == 0, bool> = true>
T& entry() { return value; }
template<std::size_t I, std::enable_if_t<I != 0, bool> = true>
auto& entry() { return Tuple<Ts...>::template entry<I - 1>(); }
// // requires C++17 support
// template<std::size_t I>
// auto& entry() {
// if constexpr (I == 0) { return value; }
// else { return Tuple<Ts...>::template entry<I - 1>(); }
// }
};
Try it on godbolt.org
You need add a template before ::entry
template<int Index> // ...................VVVVVVVVV
auto entry() -> decltype(Tuple<Types...>::template entry<Index-1>())&
{ // .......................VVVVVVVVV
return Tuple<Types...>::template entry<Index-1>();
}
or the < after ::entry is parsed as a relational operator.
But you have another problem: the specialization of entry():
template<>
Type& entry<0>()
{
return value;
}
Unfortunately you can't specialize the a method without specializing the containing class.
If you can compile C++17, you can avoid method specialization and use if constexpr
template <int Index>
auto & entry()
{
if constexpr ( Index == 0 )
return value;
else
return Tuple<Types...>::template entry<Index-1>();
}
Pre C++17 In C++14... I suppose you can solve using tag dispatching
template <int>
Type & entry_helper (std::true_type)
{ return value; }
template <int Index>
auto & entry_helper (std::false_type)
{ return Tuple<Types...>::template entry<Index-1>(); }
template <int Index>
auto & entry()
{ return entry_helper<Index>(std::integral_constant<bool, Index==0>{}); }
In C++11 you need also the trailing return type in for entry() and for the second entry_helper()
As pointed by Patrick Roberts (thanks!) the solution, adding trailing return type, works for C++11 with g++ but not for clang++, for a problem in detecting the return type in a context of recursion.
For C++11 I propose a completely different solution that avoid the entry()/entry_helper() recursion but add another level of indirection at class level (add a recursive base class struct Tpl). Add also perfect forwarding, unsigned indexes and const-versions for entry() and entry_helper().
#include <utility>
#include <iostream>
#include <type_traits>
template <std::size_t, typename...>
struct Tpl
{ void entry_helper () {} };
template <std::size_t I, typename T, typename ... Ts>
struct Tpl<I, T, Ts...> : public Tpl<I+1u, Ts...>
{
using Tpl<I+1, Ts...>::entry_helper;
Tpl (T && t, Ts && ... ts)
: Tpl<I+1u, Ts...>{std::forward<Ts>(ts)...}, value{std::forward<T>(t)}
{ }
T value;
T & entry_helper (std::integral_constant<std::size_t, I>)
{ return value; }
T const & entry_helper (std::integral_constant<std::size_t, I>) const
{ return value; }
};
template <typename ... Ts>
struct Tuple : public Tpl<0, Ts...>
{
using Tpl<0, Ts...>::entry_helper;
Tuple (Ts && ... ts) : Tpl<0u, Ts...>{std::forward<Ts>(ts)...}
{ }
template <std::size_t I>
auto entry ()
-> decltype(entry_helper(std::integral_constant<std::size_t, I>{})) &
{ return entry_helper(std::integral_constant<std::size_t, I>{}); }
template <std::size_t I>
auto entry () const
-> decltype(entry_helper(std::integral_constant<std::size_t, I>{})) const &
{ return entry_helper(std::integral_constant<std::size_t, I>{}); }
};
int main()
{
Tuple<int, int, std::string, double> t(1, 2, "Hello World", 3.4);
std::cout << t.entry<1>() << std::endl; // Prints 2
std::cout << t.entry<2>() << std::endl; // Prints "Hello World"
}
Lets say I have a tuple
std::tuple<Operation<1>, Operation<2>, Operation<3>>. Operation<> has a member function with the signature SomeType someFunction(SomeType). What I want to do is to call the operations successively such that the resulting order of calls would be Operation<3>::someFunction(Operation<2>::someFunction(Operation<1>::someFunction())) and I would get the final SomeType value. How do I achieve this using variadic templates (I have access to C++17)?
I can call each member function with std::apply([](auto& ...x) { (..., x.someFunction()); }, tuple); but what kind of expression do I need to call someFunction() with the output of the previous call?
I suppose you can combine std::apply() and template folding with a lambda as follows
auto l = [&val](auto ... Ops)
{ ((val = Ops.someFunc(val)), ...); };
The following is a full working example
#include <tuple>
#include <iostream>
template <int I>
struct Oper
{
static constexpr int someFunc (int i)
{ return i + I; }
};
int main ()
{
std::tuple<Oper<1>, Oper<2>, Oper<3>, Oper<4>> t;
int val {}; // starting value
auto l = [&val](auto ... Ops)
{ ((val = Ops.someFunc(val)), ...); };
std::apply(l, t);
std::cout << val << std::endl;
}
#max66's solution is elegant and concise, however one caveat is that all your operations must handle and return the same type (which is your case), I will try to propose a broader approach.
The idea is to rely on an overloaded operator>> to apply the desired operation on a state and the next step. To do so let's first define some building blocks:
// Just to avoid the hassle of std::forwarding by hand everywhere
#define CPPFWD(x) std::forward<decltype(x)>(x)
// We do not want to pollute the global namespace with our special operator>>
namespace combine {
// This will make the appropriate functor for each step
template <typename T, typename Op>
auto make_operation(T&& tuple_element, Op&& op) {
return [ el = CPPFWD(tuple_element),
op = CPPFWD(op) ](auto&& input) mutable {
return op(el, CPPFWD(input));
};
}
template <typename Input, typename Op>
auto operator>>(Input&& input, Op&& op) {
return CPPFWD(op)(CPPFWD(input));
}
} // ns combine
Now we are ready to tackle the left fold implementation:
template <typename State, typename Tuple, typename Op, size_t... Is>
auto fold_left_impl(State&& state, Tuple&& tuple, Op&& op, std::index_sequence<Is...>) {
using combine::make_operation;
// We want our operator>> to be in the immediate scope here
// to avoid selecting an inappropriate hypothetical overload
using combine::operator>>;
using std::get;
return (CPPFWD(state) >> ... >> make_operation(get<Is>(CPPFWD(tuple)), op));
}
Finally the function exposed to the end-user:
template <typename T>
using remove_cvref_t = std::remove_cv_t< std::remove_reference_t< T > >;
template <typename State, typename Tuple, typename Op>
auto fold_left(State&& state, Tuple&& tuple, Op&& op) {
return fold_left_impl(
CPPFWD(state),
CPPFWD(tuple),
CPPFWD(op),
std::make_index_sequence< std::tuple_size< remove_cvref_t< Tuple > >::value > {} );
}
In your case, the correct usage would be:
std::tuple<Operation<1>, Operation<2>, Operation<3>> t;
fold_left(
0,
t,
[](auto&& op, auto&& in) {
return CPPFWD(op).someFunc(CPPFWD(in));
} );
A live example can be found on Coliru
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
Is it possible to iterate over all elements in a struct or class?
For example if I have a struct of three elements of different type:
struct A {
classA a;
classB b;
classC c;
};
then I need some iterator such that a method next() would give me the value
of the next element. The problem is that as you see, the values have different types.
Nope, not with the language as it is.
You could do it by deriving your classes from a common base, and then implementing your own iterator to return pointers to each item as the iterator is traversed.
Alternatively put the items in a std::vector and use that to provide the iteration.
No, there is no reflection in C++, (yet, there are murmurs about static reflection coming one day).
Anyway, there is a way to work around this, to an extent - first of all, you'll need a (temporary) tuple with references to your data members.
Then you will need a construct "iterating" over the tuple, such as:
void applyToAll() { }
template <typename Lambda, typename... Lambdas>
void applyToAll(Lambda&& closure, Lambdas&&... closures) {
std::forward<Lambda>(closure)();
applyToAll(std::forward<Lambdas>(closures)...);
}
// use your favourite sequence-making trick
template <unsigned... Is>
struct _Sequence {
typedef _Sequence<Is...> type;
};
template <unsigned Max, unsigned... Is>
struct _MakeSequence : _MakeSequence<Max - 1, Max - 1, Is...> { };
template <unsigned... Is>
struct _MakeSequence<0, Is...> : _Sequence<Is...> { };
template <typename Tuple, typename Functor, unsigned... Is>
void _foreachElemInTuple(_Sequence<Is...>, Tuple&& t, Functor&& f) {
applyToAll(
[&]{ std::forward<Functor>(f)(std::get<Is>(std::forward<Tuple>(t))); }...
);
}
template <typename Tuple, typename Functor>
void foreachElemInTuple(Tuple&& t, Functor&& f) {
_foreachElemInTuple(
_MakeSequence<std::tuple_size<
typename std::decay<Tuple>::type>::value>(),
std::forward<Tuple>(t), std::forward<Functor>(f)
);
}
Then you can call foreachElemInTuple(yourTuple, some_adapter()).
Your adapter will look like:
struct some_adapter {
template <typename... Args>
// A little bit of C++14, you can also just -> decltype the thing
decltype(auto) operator()(Args&& ... args) const {
return doStuff(std::forward<Args>(args)...);
}
};
As everyone else says, you cannot directly iterate over data members of a
class. However, it is not difficult to do it indirectly, provided of course that
you can access each of the data members you want to iterate over. The idea
in essense, as per ScarletAmaranth's solution, is to iterate over an std::tuple
of references to those data members.
The following program shows how to obtain such a tuple, using std::forward_as_tuple,
and another way to do the iterating by compiletime recursion, without
auxiliary apparatus.
#include <tuple>
/* You want to be able do something with the values of the members of an `A`
in turn.
*/
struct A
{
char ch;
int i;
double d;
// May also have members of class type. It doesn't matter
};
/* 1) Provide yourself with the means of creating a sequence that contains
references to the data members of a given `A`
*/
std::tuple<char const &, int const &, double const &> get_A_vals(A const & a)
{
return std::forward_as_tuple(a.ch,a.i,a.d);
}
/* 2) Provide yourself with a means of applying some operation, `Func`,
to each element of an `std::tuple`
*/
template<size_t I = 0, typename Func, typename ...Ts>
typename std::enable_if<I == sizeof...(Ts)>::type
for_each_in_tuple(std::tuple<Ts...> const &, Func) {}
template<size_t I = 0, typename Func, typename ...Ts>
typename std::enable_if<I < sizeof...(Ts)>::type
for_each_in_tuple(std::tuple<Ts...> const & tpl, Func func)
{
func(std::get<I>(tpl));
for_each_in_tuple<I + 1>(tpl,func);
}
/* 3) Combine 1) and 2) to apply `Func` over the members of an `A`
*/
template<typename Func>
void for_each_in_A(A const & a, Func func)
{
for_each_in_tuple(get_A_vals(a),func);
}
// Testing...
#include <iostream>
// A specimen operation: just prints its argument
struct printer
{
template<typename T>
void operator () (T && t)
{
std::cout << t << std::endl;
}
};
int main()
{
A a{'a',1,2.0};
for_each_in_A(a,printer());
return 0;
}
// EOF
The program outputs:
a
1
2
If you have control of the structs or classes over whose members you need to
iterate, you may consider whether it is practical simply to dispense with them
and use the corresponding std::tuples everywhere.
Code built with gcc 4.8.2 and clang 3.3, -std=c++11.