Reputation: 394
In order to have a cleaner syntax, I would like to use an std::initializer_list to send a list of objects to a constructor. The objects, however, are abstract, which causes a problem: in VS 2013, it looses the vfptr reference, giving a "R6025: pure virtual function call" runtime error, and in g++ it complains that it "cannot allocate an object of abstract type ‘base’" during compilation. I surmise the compiler is trying to copy the objects (which is undesirable -- they may be big), but succeeds only in copying the base class, hence the error. My question is: Is there a solution which (1) avoids copying the objects and (2) isn't massively verbose, negating the "cleaner syntax" advantage? The code below illustrates my issue:
#include <cstdio>
#include <initializer_list>
struct base{
virtual void foo() const = 0;
};
struct derived : public base{
int i;
derived(int i) : i(i) {}
void foo() const{
printf("bar %i", i);
}
};
void foo_everything(const std::initializer_list<base> &list){
for (auto i = list.begin(), iend = list.end(); i != iend; i++) i->foo();
}
int main(void){
// Works fine
derived d(0);
base * base_ptr = &d;
base_ptr->foo();
// Does not work fine
foo_everything({ derived(1), derived(2), derived(3) });
}
Note that using base& in the template errors since std::initializer_list tries to "[form a] pointer to reference type base&", and while using base*, and then taking the address of each derived class does in-fact work, it does so by taking the address of temporaries, and thus isn't safe (g++ complains). The latter does work if I declare the derived classes outside of the method call (my provisional solution), but it still is more verbose than I hoped for.
Upvotes: 5
Views: 991
Reputation: 45484
If you want the syntax
foo_everything({ derived(1), derived(2), derived(3) });
to work, you must use a template. For example,
template<typename T>
void foo_everything(std::initializer_list<T> list)
{
for(const auto&x:list) x.foo();
}
You may add some SFINAE magic to ensure that T
is derived from base:
template<typename T>
typename std::enable_if<is_base_of<base,T>::value>::type
foo_everything(std::initializer_list<T> list)
{
for(const auto&x:list) x.foo();
}
If you want to use different derived classes in one call to foo_everything
, then you cannot use your preferred syntax
foo_everything({ derived(1), derived(2), derived(3) });
. (full stop) This is simply because std::initializer_list
wraps an array and an array must have objects of the same type. So in this strict sense, the answer is simple: it cannot be done.
Upvotes: 3
Reputation: 137404
Somewhat hackish approach using a initializer_list<base *>
:
template<class... Ts>
void foo_everything(Ts&&... args){
std::initializer_list<base *> list = {&args...};
for(auto i : list) i->foo();
}
Then remove the braces from the call:
foo_everything(derived(1), derived(2), derived(3));
If you don't really need to convert to base *
and perform a virtual call, and just want to call foo()
on each object passed in, then we can use the usual pack-expansion-inside-an-initializer-list trick:
template<class... Ts>
void foo_everything(Ts&&... args){
using expander = int[];
(void) expander { 0, ((void) std::forward<Ts>(args).foo(), 0)...};
}
Upvotes: 6