4
votes

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.

2
A std::initializer_list<base> stores, well, bases. Use a variadic template.T.C.
Or use pointers (which will be tricky in your case).Some programmer dude
Ad const std::initializer_list<base> &: The initializer_list<T> class(es) have pointer semantics, hence it is not necessary to pass them by reference.dyp
The underlying problem is that this is trying to pass an array of derived to some context where the size of derived is not known. Passing an array of pointers to derived could be possible, even if those pointers contain addresses of temporaries: the lifetime of initializer_list itself is already problematic, so this isn't really significantly more dangerous IMHO.dyp
I don't know for certain, but I would be very surprised if the standard allows std::initializer_list<abstract_class>. After all, an initializer_list<T> is a leight-weight wrapper around an array and arrays of abstract type are not possible.Walter

2 Answers

5
votes

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)...};
}
3
votes

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.