Variadic template parameter inference with nested maps - c++

Working in C++, there's some times I'm working with nested maps. For instance, hypothetically:
enum Color { RED, GREEN, BLUE};
enum Shape { CIRCLE, SQUARE, TRIANGLE };
std::unordered_map<Color, std::unordered_map<Shape, int>> shapeColorCount;
For these situations, it would be useful to use variadic templates to write setter and getter functions that are templated over the types of the keys. The goal would be something like:
template<typename TValue, typename TKeys...>
TValue& nestedGet(MapTypeHere t_map, const TKeys&... t_keys);
void nestedSet(MapTypeHere t_map, const TValue& t_value, const TKeys&... t_keys);
It's not too hard to define these functions recursively, but my main problem is getting the type inference for the template parameters to work correctly. The problem is specifying MapTypeHere. I can almost write something like
template<typename TValue, typename TKey>
using Map = std::unordered_map<TKey, TValue>;
template<typename TValue, typename TOuterKey, typename... TInnerKeys>
using Map = std::unordered_map<TOuterKey, Map<TValue, TInnerKeys...>;
template<typename TValue, typename... TKeys>
TValue& nestedGet(Map<TValue, TKeys...>& t_map, const TKeys&... t_keys);
void nestedSet(Map<TValue, TKeys...>& t_map, const TValue& t_value, const TKeys&... t_keys);
Trying to create a recursive using directive, but it complains that I'm trying to use a parameter pack in a non-pack template variable when it attempts to use the base case for Map. If I wrap them in structs, it seems to allow it to do this recursive using declaration, but then I have a problem where the type inference doesn't work. Going back to the above example:
std::unordered_map<Color, std::unordered_map<Shape, int>> shapeColorCount
nestedSet<int, Color, Shape>(shapeColorCount, 5, Color::RED, Shape::SQUARE); // This works
nestedSet(shapeColorCount, 5, Color::RED, Shape::SQUARE); // It can't figure out the types for the template
Is there a way to get this setup working correctly?

Yes, you can write the following functions:
template<typename Map, typename Value, typename FirstKey, typename ...RestKeys>
void nestedSet(Map& map, Value const& value, FirstKey const& key, RestKeys const&... rest_keys)
{
if constexpr(sizeof...(RestKeys) == 0)
map[key] = value;
else
nestedSet(map[key], value, rest_keys...);
}
template<typename Map, typename FirstKey, typename ...RestKeys>
auto& nestedGet(Map& map, FirstKey const& key, RestKeys const&... rest_keys)
{
if constexpr(sizeof...(RestKeys) == 0)
return map[key];
else
return nestedGet(map[key], rest_keys...);
}
Note that this solution doesn't depend on a particular unordered_map<Color, std::unordered_map<Shape, int>> instantiation. It works for any instantiations of key and value types, and for any depth of nested unordered_maps.
Here's a demo.
Also, if you don't have c++17, then you can rewrite the if constexpr solution with an overloaded template that takes a single KeyType parameter.

Is too late to play?
It seems to me that, given a recursive nestedGet(), that you can write using if constexpr (if you can use C++17) or also using overloading as follows,
template <typename M, typename K>
auto & nestedGet (M & map, K const & key)
{ return map[key]; }
template <typename M, typename K, typename ... RKs>
auto & nestedGet (M & map, K const & key, RKs const & ... rks)
{ return nestedGet(map[key], rks...); }
the nestedSet() function can be written upon nestedGet() simply as follows
template <typename M, typename V, typename ... Ks>
void nestedSet (M & map, V const & value, Ks const & ... keys)
{ nestedGet(map, keys...) = value; }
The following is a full compiling example
#include <iostream>
#include <unordered_map>
enum Color { RED, GREEN, BLUE};
enum Shape { CIRCLE, SQUARE, TRIANGLE };
template <typename M, typename K>
auto & nestedGet (M & map, K const & key)
{ return map[key]; }
template <typename M, typename K, typename ... RKs>
auto & nestedGet (M & map, K const & key, RKs const & ... rks)
{ return nestedGet(map[key], rks...); }
template <typename M, typename V, typename ... Ks>
void nestedSet (M & map, V const & value, Ks const & ... keys)
{ nestedGet(map, keys...) = value; }
int main ()
{
std::unordered_map<Color, std::unordered_map<Shape, int>> shapeColorCount;
nestedSet(shapeColorCount, 42, Color::RED, Shape::SQUARE);
std::cout << nestedGet(shapeColorCount, Color::RED, Shape::SQUARE) << std::endl;
}

Related

Improving fold function

I have implemented a simple fold function in C++ that accepts a lambda, and can fold multiple vectors at the same time at compile time. I am wondering if it could be simplified in some manner (I have provided both a recursive version and an iteratively recursive version - I am unsure which should have better performance): https://godbolt.org/z/39pW81
Performance optimizations are also welcome - in that regard is any of the two approaches faster?
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperR(Function&& func, const type_identity& id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I>0)
{
return func(foldHelperR<I-1>(std::forward<Function>(func), id, head, tail...), head[I], tail[I]...);
}
else
{
return func(id, head[0], tail[0]...);
}
}
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperI(Function&& func, const type_identity id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I<N-1)
{
return foldHelperI<I+1>(std::forward<Function>(func), func(id, head[I], tail[I]...), head, tail...);
}
else
{
return func(id, head[N-1], tail[N-1]...);
}
}
template<typename type_identity, typename type_head, int N_head, typename ...type_tail, int ...N_tail, typename Function = void (const type_identity&, const type_head&, const type_tail&...)>
constexpr auto fold(Function&& func, const type_identity& id, const tvecn<type_head, N_head>& head, const tvecn<type_tail, N_tail>&... tail)
{
static_assert(std::is_invocable_v<Function, const type_identity&, const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments (possibly wrong argument count).");
static_assert(all_equal_v<N_head, N_tail...>, "Vector sizes must match.");
//return foldHelperR<N_head-1>(std::forward<Function>(func), id, head, tail...);
return foldHelperI<0>(std::forward<Function>(func), id, head, tail...);
}
int main()
{
tvecn<int,3> a(1,2,3);
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
}
and can fold multiple vectors at the same time at compile time
Not exactly: if you want to operate compile-time
(1) you have to define constexpr the tvecn constructor and
(2) you have to define constexpr the foldhelper function and
(3) you have to declare constexpr a
// VVVVVVVVV
constexpr tvecn<int,3> a(1,2,3);
(4) you have to place the result of fold in a constexpr variable (or, more generally speaking, in a place where the value is required compile time, as the size field of a C-style array, or a template value parameter, or a static_assert() test)
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, a, a);
I am wondering if it could be simplified in some manner
Sure.
First of all: if you can, avoid to reinventing the weel: your tvecn is a simplified version of std::array.
Suggestion: use std::array (if you can obviously)
Second: you tagged C++17 so you can use folding
Suggestion: use it also for all_equal
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
More in general: when you have to define a custom type traits that has to provide a number, inherit (if possible) from std::integral_constant (or std::bool_constant, or std::true_type, or std::false_type: all std::integral_constant specializations). So you automatically inherit all std::integral_constant facilities.
Third: almost all C++ standard uses std::size_t, not int, for sizes.
Suggestion: when you have to do with sizes, use std::size_t, not int. This way you can avoid a lot of annoying troubles.
Fourth: from main() you should return only EXIT_SUCCESS (usually zero) or EXIT_FAILURE (usually 1)
Suggestion: avoid things as
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
Fifth: never underestimate the power of the comma operator.
Suggestion: avoid recursion at all and use template folding also for the helper function; by example
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
that you can call as follows from fold()
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
The following is a full compiling, and simplified, example
#include <array>
#include <utility>
#include <iostream>
#include <type_traits>
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
template <typename type_identity, typename type_head, std::size_t N_head,
typename ...type_tail, std::size_t ...N_tail,
typename Function = void (type_identity const &,
type_head const &,
type_tail const & ...)>
constexpr auto fold (Function && func, type_identity const & id,
std::array<type_head, N_head> const & head,
std::array<type_tail, N_tail> const & ... tail)
{
static_assert( std::is_invocable_v<Function, const type_identity&,
const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments"
" (possibly wrong argument count).");
static_assert( all_equal_v<N_head, N_tail...>,
"Vector sizes must match.");
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
}
int main()
{
constexpr std::array<int, 3u> b{2, 5, 7};
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, b, b);
std::cout << f << std::endl;
}
With Fold expression, it might be:
template <typename F, typename Init, std::size_t... Is, typename... Arrays>
constexpr auto fold_impl(F&& f, Init init, std::index_sequence<Is...>, Arrays&&... arrays)
{
auto l = [&](Init init, std::size_t i){ return f(init, arrays[i]...); };
return ((init = l(init, Is)), ...);
}
template <typename F, typename Init, typename Array, typename ... Arrays>
constexpr auto fold(F&& f, Init init, Array&& array, Arrays&&... arrays)
{
static_assert(((arrays.size() == array.size()) && ...));
return fold_impl(f, init, std::make_index_sequence<array.size()>{}, array, arrays...);
}
Demo

Template function to extract value out of several nested unordered_map's

Let's assume I have a nested std::unordered_map that looks like this:
std::unordered_map<ResourceName, std::unordered_map<HAL::ResourceFormat::Color, HAL::RTDescriptor>>
I want a function that will return a pointer to HAL::RTDescriptor based on two keys ResourceName and HAL::ResourceFormat::Color if object is present or nullptr otherwise. The straightforward implementation looks like this:
const HAL::RTDescriptor* ResourceDescriptorStorage::GetRTDescriptor(ResourceName resourceName, HAL::ResourceFormat::Color format) const
{
auto mapIt = mRTDescriptorMap.find(resourceName);
if (mapIt == mRTDescriptorMap.end()) {
return nullptr;
}
auto& nestedMap = mapIt->second;
auto nestedMapIt = nestedMap.find(format);
if (nestedMapIt == nestedMap.end()) {
return nullptr;
}
return &nestedMapIt->second;
}
Is there a way to use templates to generalize the logic?
Something with parameter packs for keys. Something that will go through each nested container, check for object availability and return it or nullptr at the end:
template<
template<class...> class AssociativeContainer,
class... Keys
>
decltype(auto) Find(const AssociativeContainer<...>& rootContainer, Keys&&... keys)
{
...
}
Simpler solution (requires C++17):
template<class AssociativeContainer, class Key, class... Keys>
auto Find(const AssociativeContainer& container, Key&& key, Keys&&... keys){
auto it = container.find(std::forward<Key>(key));
bool found = it != container.end();
if constexpr(sizeof...(Keys) == 0)
return found ? &it->second : nullptr;
else
return found ? Find(it->second, std::forward<Keys>(keys)...) : nullptr;
}
This also allows to get a reference to any inbetween container, as it doesn't require to pass all keys.
Is there a way to use templates to generalize the logic? Something with parameter packs for keys. Something that will go through each nested container, check for object availability and return it or nullptr at the end:
Require a little of work (maybe someone more expert than me can make it simpler) but sure it's possible.
By example... given a custom type traits (and using to simplify the use) as follows
template <typename T>
struct lastType
{ using type = T; };
template <template <typename...> class C, typename K, typename V>
struct lastType<C<K, V>> : public lastType<V>
{ };
template <typename T>
using lastType_t = typename lastType<T>::type;
you can write Find() recursively as follows
// ground case
template <typename V>
V const * Find (V const & val)
{ return &val; }
// recursion case
template <typename C, typename K0, typename ... Ks>
lastType_t<C> const * Find (C const & cnt, K0 && key0, Ks && ... keys)
{
auto mapIt = cnt.find(std::forward<K0>(key0));
if ( mapIt == cnt.cend() )
return nullptr;
return Find(mapIt->second, std::forward<Ks>(keys)...);
}
The following is a full compiling example
#include <map>
#include <string>
#include <iostream>
#include <unordered_map>
template <typename T>
struct lastType
{ using type = T; };
template <template <typename...> class C, typename K, typename V>
struct lastType<C<K, V>> : public lastType<V>
{ };
template <typename T>
using lastType_t = typename lastType<T>::type;
template <typename V>
V const * Find (V const & val)
{ return &val; }
template <typename C, typename K0, typename ... Ks>
lastType_t<C> const * Find (C const & cnt, K0 && key0, Ks && ... keys)
{
auto mapIt = cnt.find(std::forward<K0>(key0));
if ( mapIt == cnt.cend() )
return nullptr;
return Find(mapIt->second, std::forward<Ks>(keys)...);
}
using typeC = std::map<int,
std::unordered_map<std::string,
std::unordered_map<long,
std::map<char, long long>>>>;
int main ()
{
typeC c;
c[0]["one"][2l]['3'] = 4ll;
auto v = Find(c, 0, "one", 2l, '3');
std::cout << (*v) << std::endl;
static_assert( std::is_same_v<decltype(v), long long const *>, "!" );
}
-- EDIT --
I'm particularly dumb today: as highlighted by krisz in his answer (thanks), the ternary operator permit the use of auto as return type (from C++14).
So there is no needs of the lastType custom type traits and Find() can be simply written as
// ground case
template <typename V>
V const * Find (V const & val)
{ return &val; }
// recursion case
template <typename C, typename K0, typename ... Ks>
auto Find (C const & cnt, K0 && key0, Ks && ... keys)
{
auto mapIt = cnt.find(std::forward<K0>(key0));
return mapIt == cnt.cend()
? nullptr
: Find(mapIt->second, std::forward<Ks>(keys)...);
}
For C++11, the recursion case require also the trailing return type; by example
-> decltype(Find(cnt.find(std::forward<K0>(key0))->second, std::forward<Ks>(keys)...))

C++ variadic template

I am trying to create a polymorhic container working with variadic templates.
Container is initialized as
container<tag, std::string, int, int, int> m;
I want to use following syntax:
auto& v2 = m.find<2, 3, 4>(255, 0, 0);
Template arguments would specify "columns" and for parameters, I want appropriate type to be expected by compiler.
For one template argument (find<2>(255)) I used:
template < int idx > const typename value_type &
find( const typename std::tuple_element<idx, typename value_type>::type &key) {
const std::size_t row_id = getId<idx>(key);
return data_.at(row_id);
}
That worked perfectly, so I wanted to expand it as follows:
template<int ... idx> const typename value_type &
find(const typename std::tuple_element<idx..., typename value_type>::type &keys...) {
const std::size_t row_id = getId<idx...>(keys);
return data_.at(row_id);
}
What's not working at all. Compilation error C2660 - find: function does not take 3 arguments. Can someone explain me, what am I missing here?
Thanks.
EDIT:
Header of container class is
template<typename ... Arguments> class container
value_typementioned is
typedef std::tuple < Arguments... > value_type;
EDIT2:
T.C.'s answer was indeed useful, though I'm still crawling through my bugs with variadic templates. Currently:
enum tag {/*...*/}
int main() {
container<tag, std::string, int, int, int> m;
}
template<typename ... Arguments> class container {
public:
typedef std::tuple < Arguments... > value_type;
std::vector<value_type> data_;
template <int id> void narrowRange(
std::set<std::size_t> & range,
const typename std::tuple_element<id, typename value_type>::type &key)
{
// all commented out
}
template <int id, int ... idx>
void narrowRange(
std::set<std::size_t> & range,
const typename std::tuple_element<id, typename value_type>::type & key,
const typename std::tuple_element<idx, typename value_type>::type & ... keys) // <-
{
narrowRange<idx...>(range, keys...);
// rest commented out
}
Will invoke internal error in MSVS2013 on the marked line. Any suggestions why would be appreciated.
First, value_type doesn't need typename - I'm fairly sure the grammar actually bans it.
Second, you are expanding idx too early, and also incorrectly attempting to expand keys in the declaration. (That second ... is actually being parsed as a C-style varargs.) You are also not expanding the pack keys in the function body. Assuming that you want find<2, 3, 4>(255, 0, 0) to call getId<2, 3, 4>(255, 0, 0), the correct syntax is
template<int ... idx> const value_type &
find(const typename std::tuple_element<idx, value_type>::type &... keys) {
const std::size_t row_id = getId<idx...>(keys...);
return data_.at(row_id);
}

Should std::hash<T> work when T is std::pair<two simpler types also supported by std::hash>?

I was using an ordered set declared as so:
std::set<std::pair<const std::string, const myClass *> > myset;
After doing some analysis of the way I was using the set, I concluded that an unordered_set would be a smarter choice. But when I changed std::set to std::unordered_set, I got a vast spew of error messages from my compiler (g++ 4.8.1) complaining of an
invalid use of incomplete type struct std::hash<std::pair<const std::basic_string<char>, const myClass * > >
I figured out that std::hash didn't know how to deal with a type which was std::pair, despite the fact that the two types that made up the pair were each hashable. I think error for hash function of pair of ints contains relevant information about the C++11 standard that explains why things went awry. (There's no good explanation for the impenetrable wall of error text that g++ emits for this.)
It would seem to me that
std::hash<std::pair<T1, T2>> hasher(make_pair(x,y))
= some_func(std::hash<T1>hasher(x), std::hash<T2>hasher(y) )
where some_func() could be as simple as XOR (or not; see Why is XOR the default way to combine hashes?)
Is there a good reason for the standard to not require std::hash to know how to construct a hash value for an object which is a pair of types that are each hashable?
The reason is simple, it was not added to the standard. The same is true of hashing other structures like tuple.
Things tend to be added to the standard when they are good enough, not when they are perfect, as perfection is the enemy of the good. More specializations of std::hash are not things that will break code (that often), so adding new ones is relatively harmless.
In any case, to that end, we can write our own hash extenders. As an example:
namespace hashers {
constexpr size_t hash_combine( size_t, size_t ); // steal from boost, or write your own
constexpr size_t hash_combine( size_t a ) { return a; }
constexpr size_t hash_combine() { return 0; }
template<class...Sizes>
constexpr size_t hash_combine( size_t a, size_t b, Sizes... sizes ) {
return hash_combine( hash_combine(a,b), sizes... );
}
template<class T=void> struct hash;
template<class A, class B>
constexpr size_t custom_hash( std::pair<A,B> const& p ) {
return hash_combine( hash<size_t>{}(2), hash<std::decay_t<A>>{}(p.first), hash<std::decay_t<B>>{}(p.second) );
}
template<class...Ts, size_t...Is>
constexpr size_t custom_hash( std::index_sequence<Is...>, std::tuple<Ts...> const& p ) {
return hash_combine( hash<size_t>{}(sizeof...(Ts)), hash<std::decay_t<Ts>>{}(std::get<Is>(p))... );
}
template<class...Ts>
constexpr size_t custom_hash( std::tuple<Ts...> const& p ) {
return custom_hash( std::index_sequence_for<Ts...>{}, p );
}
template<class T0, class C>
constexpr size_t custom_hash_container( size_t n, C const& c) {
size_t retval = hash<size_t>{}(n);
for( auto&& x : c)
retval = hash_combine( retval, hash<T>{}(x) );
return retval;
}
template<class T0, class C>
constexpr size_t custom_hash_container( C const& c) {
return custom_hash_container( c.size(), c );
}
template<class T, class...Ts>
size_t custom_hash( std::vector<T, Ts...> const& v ) {
return custom_hash_container<T>(v);
}
template<class T, class...Ts>
size_t custom_hash( std::basic_string<T, Ts...> const& v ) {
return custom_hash_container<T>(v);
}
template<class T, size_t n>
constexpr size_t custom_hash( std::array<T, n> const& v ) {
return custom_hash_container<T>(n, v);
}
template<class T, size_t n>
constexpr size_t custom_hash( T (const& v)[n] ) {
return custom_hash_container<T>(n, v);
}
// etc -- list, deque, map, unordered map, whatever you want to support
namespace details {
template<class T, class=void>
struct hash : std::hash<T> {};
using hashers::custom_hash;
template<class T>
struct hash<T,decltype(void(
custom_hash(declval<T const&>())
)) {
constexpr size_t operator()(T const& t)const {
return custom_hash(t);
}
};
}
template<class T>
struct hash : details::hash<T> {};
template<>
struct hash<void> {
template<class T>
constexpr size_t operator()(T const& t)const { return hash<T>{}(t); }
}
}
and now hashers::hash<T> will recursively use either an ADL-looked up custom_hash function, or std::hash if that fails, to hash T and its components, and hashers::hash<> is a universal hasher that tries to hash anything passed to it.
Code may not compile as shown.
I chose to hash all containers and tuples as hash their length, followed by hashing the combination of their contents. As a side effect, array<int, 3> hashes the same as tuple<int,int,int>, and tuple<int,int> hashes the same as pair<int,int>, and std::vector<char>{'a','b','c', '\0'} hashes the same as "abc", which I think is a nice property. The empty array/tuple/vector/etc hashes like size_t(0).
You can extend the above system for your own types by simply overriding custom_hash in the namespace of the type in question, or specializing either std::hash<X> or hashers::hash<X> to do your custom hash (I would go with std::hash for the principle of least surprise myself). For advanced use, you can specialize hashers::details::hash<X,void> with SFINAE, but I'd say do it for custom_hash instead.

Is it possible to iterate over all elements in a struct or class?

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.