Iterating over different types
Asked Answered
H

9

66

Given the following code:

struct Window{
    void show();
    //stuff
}w1, w2, w3;

struct Widget{
    void show();
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show();
    //stuff
}t1, t2, t3;

I want to show a bunch of items:

for (auto &obj : {w3, w4, w5, t1})
    obj.show();

However this does not compile since the std::initializer_list<T> in the for-loop cannot deduce T and in fact there is not really a T that would fit. I don't want to create a type erasure type because of the amount of code required and the unnecessary runtime overhead. How do I correctly write my loop so that the type of obj is deduced for every item in the conceptual list separately?

Humfrey answered 16/12, 2015 at 14:8 Comment(8)
Use a tupleAffectionate
@LogicStuff I can't really make all classes inherit from something with a virtual void show(), it should also work with .size() and std-containers.Humfrey
Is the list a run time or compile time one? you can always expand multiple calls to the same function like hereInsuppressible
The optimal solution depends on whether that list of things you want to iterate over is fixed or variable.Raymonraymond
It is a fixed list. Using the link from @Affectionate I got this working to my satisfaction. Maybe write an answer?Humfrey
@Humfrey it is acceptable to answer your own question once you figure it out, and it sounds like your solution is rather different from Richard Hodge's.Affectionate
From the example, it looks like Window, Toolbar and Widget should be derived from Showable. The answers with variadic templates, while fancy, are the horror from a maintenance point of view. Maybe not that example in isolation, but a program that has 10s or 1000s of such constructs, ..., I would be out.Trot
FYI: quoted this question in another question of my own. Enjoy: #34361088Raymonraymond
P
64

In C++17 or better you'd use fold expressions, to "walk through" your heterogenous arguments applying the member function:

auto Printer = [](auto&&... args) {
    (args.show(), ...);
};

Printer(w1, w2, w3, w4, w5, w6, t1, t2, t3);

Demo

You can read more on this in my blog

Pulitzer answered 16/12, 2015 at 15:11 Comment(5)
@RichardHodges You don't get more modern than thatPulitzer
How the heck does that work? Is the lambda equivalent to a functor containing a template function?Selfimmolating
@immibis that's exactly what it isPulitzer
@NikosAthanasiou Nevertheless, it is not "modern", it is "future". Modern C++ usually refers to a currently accepted standard, which is of now C++14.Halfmast
@Halfmast Folds are accepted into the language (the fact that the next Standard is not yet released does not change that, there will be no more committee discussions on whether to incorporate folds or not) and implementations already exist that have folds (clang and g++6 - plus you can access clang in VS15). And this not even a case like #pragma once which is not Standard c++ but all major compilers have it (folds are not an extension)Pulitzer
R
40

boost::fusion is awesome but oldskool - it caters for the deficiencies in c++03.

c++11's variadic template expansion to the rescue!

#include <iostream>

struct Window{
    void show() {
        std::cout << "Window\n";
    }
    //stuff
}w1, w2, w3;

struct Widget{
    void show() {
        std::cout << "Widget\n";
    }
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show()
    {
        std::cout << "Toolbar\n";
    }
    //stuff
}t1, t2, t3;


template<class...Objects>
void call_show(Objects&&...objects)
{
    using expand = int[];
    (void) expand { 0, ((void)objects.show(), 0)... };
}

auto main() -> int
{
    call_show(w3, w4, w5, t1);
    return 0;
}

expected output:

Window
Widget
Widget
Toolbar

another, more generic way (requires c++14):

// note that i have avoided a function names that look like
// one in the standard library.

template<class Functor, class...Objects>
void for_all(Functor&& f, Objects&&... objects)
{
    using expand = int[];
    (void) expand { 0, (f(std::forward<Objects>(objects)), 0)... };

}

called like so:

for_all([](auto& thing) { thing.show(); }, w3, w4, w5, t1);
Raymonraymond answered 16/12, 2015 at 14:29 Comment(8)
Funny how you consider boost::fusion oldskool, yet you use a C-style cast. Double standards?Glennisglennon
@MaximEgorushkin :) this is one of the few times when a c-style cast is appropriate, but I can modify it to not use one if necessary. The casts are there to suppress compiler warnings in case your functor returns a value (which is then unused)Raymonraymond
There are two camps: people who use C-style casts and people who do not. I like the second camp because it eliminated a class of errors resulting from bad judgement when a C-style cast is appropriate.Glennisglennon
@MaximEgorushkin I of course agree with you. There is no place for c-style casts in almost any code. the static_cast version would look like this: static_cast<void>(expand { 0, (static_cast<void>(objects.show()), 0)... }); Not sure whether that improves clarity or reduces it. What do you think?Raymonraymond
I always write static_cast<void> to suppress unused variable/value warnings, but some of my colleagues do not like it. But I transcend such aversions :)))Glennisglennon
You need the (void) cast in the braced-init-list to suppress overloaded commas anyway.Hightension
I wonder why you: 1. Use trailing-return-type exactly once, and that for main of all possibilities. 2. Don't take advantage of the implicit return 0; in main.Carlton
@Carlton in all honesty it's utter laziness. I have a self-compiling template file called skeleton.cpp that I copy to a new file for trying out these little demos. Then I push the file into a git repo called "play" just in case someone asks me a question a few weeks later when I've completely forgotten what I was saying.Raymonraymond
G
21

Another option is to use boost::tuple or std::tuple and boost::fusion::for_each algorithm:

#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/adapted/boost_tuple.hpp>

boost::fusion::for_each(
    boost::tie(w1, w2, w3, w4, w5, w6, t1, t2, t3), // by reference, not a copy
    [](auto&& t) { t.show(); } 
    );

Just out of curiosity, compared the generated assembly output of Richard Hodges's method with the above. With gcc-4.9.2 -Wall -Wextra -std=gnu++14 -O3 -march=native the produced assembly code is identical.

Glennisglennon answered 16/12, 2015 at 14:39 Comment(5)
That's comforting to know. on my installation of apple clang 7.0 with -O3, the compiler has inlined everything into a series of calls to cout::operator<<. i.e. absolutely zero overhead. If boost does that too it's a testament to the fantastic guys who maintain the library.Raymonraymond
@RichardHodges I agree. Easy to use, portable and as fast as non-portable solutions :)))Glennisglennon
Which answer is non-portable?Spectrochemistry
@Spectrochemistry This answer works for C++98 and on. Provided the lambda is replaced with a callable object,Glennisglennon
Ah, between C++ standard versions then; I thought you meant someone's answer was compiler or platform-specific, and I wasn't seeing it.Spectrochemistry
H
15

Based on https://mcmap.net/q/17208/-how-can-you-iterate-over-the-elements-of-an-std-tuple this works without creating an extra function, boost or inheritance.

Header:

#include <tuple>
#include <utility> 

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_each(const std::tuple<Tp...> &, FuncT) // Unused arguments are given no names.
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_each(const std::tuple<Tp...>& t, FuncT f)
  {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
  }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...> &&, FuncT) // Unused arguments are given no names.
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...>&& t, FuncT f)
  {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(std::move(t), f);
  }

.cpp:

struct Window{
    void show(){}
    //stuff
}w1, w2, w3;

struct Widget{
    void show(){}
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show(){}
    //stuff
}t1, t2, t3;

int main() {
    for_each(std::tie(w3, w4, w5, t1), [](auto &obj){
        obj.show();
    });
}
Humfrey answered 16/12, 2015 at 14:36 Comment(2)
you are copying w3, w4, w5, t1 when calling make_tuple. it seems like too much of an overhead to copy instances just to print them. DemoInconsistency
@LorahAttkins You are right. Fortunately it also works with std::tie, so the copies are avoidable. Fixed.Humfrey
U
11

Window, Widget and Toolbar share common interface, so you can create abstract class and make other classes inherit from it:

struct Showable {
    virtual void show() = 0; // abstract method
};

struct Window: Showable{
    void show();
    //stuff
}w1, w2, w3;

struct Widget: Showable{
    void show();
    //stuff
}w4, w5, w6;

struct Toolbar: Showable{
    void show();
    //stuff
}t1, t2, t3;

Then, you can create array of pointers to Showable, and iterate over it:

int main() {
    Showable *items[] = {&w3, &w4, &w5, &t1};
    for (auto &obj : items)
        obj->show();
}

See it working online

Ulphi answered 16/12, 2015 at 16:3 Comment(5)
That comes with a runtime cost (maybe the calls get devirtualized), requires modification of all classes and it is not feasible to create a common base class for every function. Additionally it doesn't work for buildins, member variables and std-containers with .size(). But generally you are right, this is the traditional solution.Humfrey
For runtime dispatch, why not just std::bind the member function calls into an array of std::function<void()>? No virtual inheritance on the widget required.Raymonraymond
@nwp: It comes at a tiny, tiny, tiny, microscopic runtime cost that mostly vanishes within loops or when the show-function is non-trivial. Your solution comes at a paytime cost. In a business, it's in many cases the more expensive solution, both at programming-time as well as maintenance-time. All solutions have their advantages and disadvantages. Does tie() still work once the customer wishes to have flexible user interfaces, for example as seen million times in typical dashboards?Trot
I'm up-voting this regardless of how OP find run-time overhead annoying, because this also make maintenance of software overall much better, it also relate the three classes which are not arbitrary classes that has nothing to do with each others except for having a show function, which is good for a well engineered system. Also it ensure input follows the interface forcing it to have the required function preventing potential issue where it should never.Lunt
@KhaledAKhunaifer : It's also intrusive – is it not silly to change an entire class hierarchy just so someone can have a more "conventional" for-loop somewhere?Spectrochemistry
G
8

I recommend Boost.Hana, which IMHO is the best and most flexible template meta-programming library available.

#include <boost/hana/ext/std/tuple.hpp>
#include <boost/hana.hpp>

namespace hana = boost::hana;

hana::for_each(std::tie(w3, w4, w5, t1), [](auto& obj) { obj.show(); });
Gastrology answered 16/12, 2015 at 15:38 Comment(12)
@Richard There may be a std::tie equivalent in the library, but I don't have the time to find it right now. If I find one I'll update.Gastrology
May be it is the best and most flexible but this usage looks too verbose.Glennisglennon
@Maxim There you are :)Gastrology
Now, how is this better than using boost::fusion?Glennisglennon
@Maxim Hana is a header-only library for C++ metaprogramming suited for computations on both types and values. The functionality it provides is a superset of what is provided by the well established Boost.MPL and Boost.Fusion libraries. By leveraging C++11/14 implementation techniques and idioms, Hana boasts faster compilation times and runtime performance on par or better than previous metaprogramming libraries, while noticeably increasing the level of expressiveness in the process.Gastrology
I fail to see the difference in this particular example. And the library does not seem to be available in current boost-1.59.Glennisglennon
@Maxim The fact that hana is more flexible and compiles faster is good enough of a reason to prefer it over boost.fusionGastrology
@Maxin take a look at the link I've provided in my answer.Gastrology
Do I understand it correctly that Hana is not part of Boost, despite its name, location and namespace?Wisp
@Rusian It'll join the official boost in (I believe) the next release. Until then, you'll have to download and include the headers manually (at least it's a header-only library!)Gastrology
@Wisp it's on GitHub in the boostorg account. I downloaded and installed it I to my local boost directory last night. Very easy if you're familiar with cmake. It's a very nice library. Now I'm trying to think of a real problem to solve with it :)Raymonraymond
@Wisp Hana will be in Version 1.61.0 and is currently available in the beta!Gastrology
H
4

I think boost::variant is worth mentioning. All the more it has chances to become std::variant in C++17.

int main()
{
  std::vector<boost::variant<Window*, Widget*, Toolbar*>> items = { &w1, &w4, &t1 };

  for (const auto& item : items)
  {
    boost::apply_visitor([](auto* v) { v->show(); }, item);
  }
  return 0;
}
Halfmast answered 17/12, 2015 at 17:4 Comment(4)
I love variants, but this is just an unnecessary indirection. std::tuple is already in C++11.Spectrochemistry
How do you suggest to use std::tuple here?Halfmast
boost::fusion::for_each. I mean, if we're bringing in Boost either way, we may as well stick with the most appropriate data structure. :-]Spectrochemistry
@Spectrochemistry Agree, this might be a more appropriate option.Halfmast
G
0

A late answer but here is general solution with C++14 which works like the boost::fusion::for_each but doesn't require Boost:

#include <tuple>

namespace detail {
template<typename Tuple, typename Function, std::size_t... Is>
void tuple_for_each_impl(Tuple&& tup, Function&& fn, std::index_sequence<Is...>) {
    using dummy = int[];
    static_cast<void>(dummy {
        0, (static_cast<void>(fn(std::get<Is>(std::forward<Tuple>(tup)))), 0)...
    });
}
}

template<typename Function, typename... Args>
void tuple_for_each(std::tuple<Args...>&& tup, Function&& fn) {
    detail::tuple_for_each_impl(std::forward<std::tuple<Args...>>(tup),
            std::forward<Function>(fn), std::index_sequence_for<Args...>{});
}

int main() {
    tuple_for_each(std::tie(w1, w2, w3, w4, w5, w6, t1, t2, t3), [](auto&& arg) {
        arg.show();
    });
}

If you want to achieve more or less the same thing without the std::tuple, you can create a single-function variant of the above code:

#include <utility>

template<typename Function, typename... Args>
void va_for_each(Function&& fn, Args&&... args) {
    using dummy = int[];
    static_cast<void>(dummy {
        0, (static_cast<void>(fn(std::forward<Args>(args))), 0)...
    });
}

int main() {
    auto action = [](auto&& arg) { arg.show(); };
    va_for_each(action, w1, w2, w3, w4, w5, w6, t1, t2, t3);
}

The drawback of the second example is that it requires to specify the processing function first, therefore doesn't have the same look like the well known std::for_each. Anyway with my compiler (GCC 5.4.0) using -O2 optimization level, they produce the same assembly output.

Gaylord answered 14/7, 2017 at 9:37 Comment(0)
A
0

You could type erase the problem. This has the advantage that you can split the execution of the operation from the generation of the list.

template<class T, auto Op, class R, class...Args>
concept opable = requires(T& t, Args&&...args) {
  { Op(t, std::forward<Args>(args)...) }->std::convertible_to<R>;
};
  
template<auto Op, class Sig=void()>
struct opable_ref;

template<auto Op, class R, class...Args>
struct opable_ref<Op, R(Args...)> {
  template<opable<Op, R, Args...> U>
  opable_ref( U& u ):
    pv(std::addressof(u)),
    pf([](void const*pv, Args&&...args)->R{
      return Op( *static_cast<U*>(const_cast<void*>(pv)), std::forward<Args>(args)... );
    })
  {}
  R operator()(Args...args)const{
    return pf(pv, std::forward<Args>(args)...);
  }
  // sugar:
  auto operator->*( decltype(Op) const& ) const {
    return [pf=pf, pv=pv](Args...args)->R {
      return pf(pv, std::forward<Args>(args)...);
    };
  }
private:
  void const* pv = nullptr;
  R(*pf)(void const*, Args&&...) = nullptr;
};

auto do_show = [](auto&& x)->void {
  x.show();
};

using showable_ref = opable_ref<do_show>;

A showable_ref is a reference to any object that can be shown.

template<class T>
using il = std::initializer_list<T>;

for (auto &obj : il<showable_ref>{w3, w4, w5, t1})
  (obj->*do_show)();

or

for (auto &obj : il<showable_ref>{w3, w4, w5, t1})
  obj();

Live example.

opable_ref<lambda, Sig> creates an object that represents a reference to something you could apply the lambda to, providing extra arguments as described in Sig and the return value described in Sig (defaulting to no extra arguments and returning void).

At the point of construction, it remembers how to invoke Op on the object, assuming you have a void pointer pointing at it, and stores a void pointer to the object.

A more robust, library-worthy version might split off that type erasure from the void pointer; we convert our lambda into something that can remember a type erased argument.

This would allow us to have a collection of type erased operations all acting on the same type erased object, like:

template<class T, auto Op, class R, class...Args>
concept opable = requires(T& t, Args&&...args) {
  { Op(t, std::forward<Args>(args)...) }->std::convertible_to<R>;
};
  
template<class T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag={};

template<auto Op>
struct op_t{
  template<class...Args>
  decltype(auto) operator()(Args&&...args)const {
    return op(std::forward<Args>(args)...);
  }
};
template<auto Op>constexpr op_t<Op> op={};

template<auto Op, class Sig=void()>
struct eraseable;
  
template<auto Op, class R, class...Args>
struct eraseable<Op, R(Args...)> {
  template<opable<Op, R, Args...> T>
  eraseable( tag_t<T> ):
    pf([](void const*pv, Args&&...args)->R{
      return Op( *static_cast<T*>(const_cast<void*>(pv)), std::forward<Args>(args)... );
    })
  {}
  auto operator->*( op_t<Op> op )const {
    return [pf=pf](void const* pv, Args...args)->R {
      pf(pv, std::forward<Args>(args)...);
    };
  }

  R operator()(void const* pv, Args...args)const {
    return ((*this)->*op<Op>)(pv, std::forward<Args>(args)...);
  }
private:
  R(*pf)(void const*, Args&&...)=nullptr;
};

template<class...erasure>
struct opable_ref {
  template<class T>
  opable_ref(T& t):
    pv(std::addressof(t)),
    operations(tag<T>)
  {}
  template<auto Op>
  decltype(auto) operator->*( op_t<Op> )const {
    return [pv=pv, ops=operations]<class...Args>(Args&&...args)->decltype(auto) {
      return (ops->*op<Op>)(pv, std::forward<Args>(args)...);
    };
  }
private:
  struct operations_t:
    public erasure...
  {
    template<class T>
    operations_t(tag_t<T> tag):
      erasure(tag)...
    {}
    using erasure::operator->*...;
  };
  void const* pv = nullptr;
  operations_t operations;
};

auto do_show = [](auto&x){x.show();};
auto do_hide = [](auto&x){x.hide();};
constexpr auto show = op<do_show>;
constexpr auto hide = op<do_hide>;

using erase_show = eraseable<do_show>;
using erase_hide = eraseable<do_hide>;
using showable_ref = opable_ref<erase_show, erase_hide>;

and now we can erase both show and hide, with point-of-use syntax looking like:

for (auto &obj : il<showable_ref>{w3, w4, w5, t1})
{
  (obj->*show)();
  (obj->*hide)();
}

Live example.

The opable_ref takes up the space of 1 pointer, plus 1 (raw C function) pointer per operation. We could move the raw C function pointers into a table, where they only exist per type erased into opable_ref - a vtable - with a static local trick.

template<class...erasure>
struct opable_ref {
  template<class T>
  opable_ref(T& t):
    pv(std::addressof(t)),
    operations(get_vtable(tag<T>))
  {}
  template<auto Op>
  decltype(auto) operator->*( op_t<Op> )const {
    return [pv=pv, ops=operations]<class...Args>(Args&&...args)->decltype(auto) {
      return ((*ops)->*op<Op>)(pv, std::forward<Args>(args)...);
    };
  }
private:
  struct operations_t:
    public erasure...
  {
    template<class T>
    operations_t(tag_t<T> tag):
      erasure(tag)...
    {}
    using erasure::operator->*...;
  };
  void const* pv = nullptr;
  operations_t const* operations = nullptr;
  template<class T>
  static operations_t const* get_vtable(tag_t<T> tag) {
    static operations_t ops(tag);
    return &ops;
  }
};

overhead per opable_ref is now 2 pointers, plus a table of one function pointer per type and per operation erased.

Attah answered 14/6 at 14:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.