Reputation: 9991
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
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();
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)();
}
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
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);
You can read more on this in my blog
Upvotes: 64
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
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
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
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
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();
}
Upvotes: 11
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
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