nwp
nwp

Reputation: 9991

Iterating over different types

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?

Upvotes: 66

Views: 13227

Answers (9)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

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.

Upvotes: 0

Nikos Athanasiou
Nikos Athanasiou

Reputation: 31519

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

Upvotes: 64

Akira
Akira

Reputation: 4473

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.

Upvotes: 0

Mikhail
Mikhail

Reputation: 21749

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;
}

Upvotes: 4

nwp
nwp

Reputation: 9991

Based on https://stackoverflow.com/a/6894436/3484570 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();
    });
}

Upvotes: 15

Brian Rodriguez
Brian Rodriguez

Reputation: 4359

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(); });

Upvotes: 8

GingerPlusPlus
GingerPlusPlus

Reputation: 5606

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

Upvotes: 11

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136246

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.

Upvotes: 21

Richard Hodges
Richard Hodges

Reputation: 69864

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);

Upvotes: 40

Related Questions