0

Suppose I have class A and two derived classes, B and C, e.g.:

#include <iostream>
#include <list>
#include <string>

using namespace std;

class A {
public:
  virtual void poke() const = 0;
  virtual ~A() {};
};

class B : public A {
  string _response;
public:
  B(const string& response) : _response(response) {}
  void poke () const {
    cout << _response << endl;
  }
};

class C : public A {
  string _response;
public:
  C(const string& response) : _response(response) {}
  void poke () const {
    cout << "Well, " << _response << endl;
  }
};

Can I somehow initialize an std::list using the following initializer list: {B("Me"), C("and you")}, so that polymorphism works when I iterate over the list and call poke() (i.e., no slicing occurs)? I guess I need to define an std::list<Smth>, where Smth accepts temporary objects, has a copy constructor that does move semantics inside (because initialization lists seem to be doing copying and not moving), and supports smart pointers so I can iterate with it and do (*it)->poke(). Just for clarity, I want to be be able to write:

list<Smth> test {B("Me"), C("and you")};
for(auto it = test.begin(); it != test.end(); it++) {
  (*it)->poke();
}

I was trying to find a simple solution but I got to the point where my program compiled but generated run time errors, and so I gave up at that point... Maybe somehow make a unique pointer out of a temporary object? Or can I use && somehow?

Andrei
  • 2,585
  • 1
  • 14
  • 17

2 Answers2

1

For polymorphism, you need a reference or a pointer. Both will become dangling as soon as the sentence ends, because even if you bound those objects to them somehow, you bound them to temporary objects. The usual solution is to dynamically allocate and create the objects and holding them with pointers. This means something like the following (I also changed the loop to C++11 style, instead of using iterators directly):

std::list<std::unique_ptr<A>> test {
    std::make_unique<B>("Me"), std::make_unique<C>("and you")};
for(const auto& p : test) {
    p->poke();
}
Yehezkel B.
  • 1,140
  • 6
  • 10
  • Thanks. I now this solution, my question is a bit different: is it not possible to define class Smth so that a simpler notation {B("Me"), C("and you")} works with list? The temporary objects are not destroyed if you create a reference to them. So I thought maybe it is possible to keep them alive that way. Or is it really non-standard / not possible? – Andrei Oct 17 '16 at 21:55
  • One more comment, this particular code does not compile. I think you're not allowed to use unique_ptr in the initializer list directly, because the elements of the initializer list are copied, not moved. (Only test.push_back(...) works.) – Andrei Oct 17 '16 at 22:04
  • Well, seems I do have an issue with the code sample, but anyway this is not what you are interested with. – Yehezkel B. Oct 18 '16 at 13:08
  • Temporaries are left alive when you hold them with a const reference or with an rvalue-reference but in both cases, it must be a function-level reference. You can't keep a temporary alive by a reference (even a const or rvalue one) which is a member variable of an object. (If that was possible, you would be able to use reference_wrapper, which one of its uses is for enabling STL container to hold references, but as I said, it's impossible.) – Yehezkel B. Oct 18 '16 at 13:11
  • (Reference: http://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary) – Yehezkel B. Oct 18 '16 at 13:16
0

Well, if I am willing to do extra copying of B and C and use a shared_ptr instead of a unique_ptr, then the following example works (I am not saying it is good programming style, but it does show the cost of having a convenient notation with initialization lists):

#include <iostream>
#include <list>
#include <memory>
#include <string>

using namespace std;

class A {
public:
  virtual void poke() const = 0;
  virtual ~A() {}
};

class B : public A {
  string _response;
public:
  B(const string& response) : _response(response) {}
  void poke () const {
    cout << _response << endl;
  }
  operator shared_ptr<A>() {
    return make_shared<B>(*this);
  }
};

class C : public A {
  string _response;
public:
  C(const string& response) : _response(response) {}
  void poke () const {
    cout << "Well, " << _response << endl;
  }
  operator shared_ptr<A>() {
    return make_shared<C>(*this);
  }
};

int main() {
  list<shared_ptr<A>> test {B("Me"), C("and you")};

  for(const auto& it : test) {
    it->poke();
  }
}
Andrei
  • 2,585
  • 1
  • 14
  • 17
  • So you prefer the evil of implicit conversion over a bit longer notation of using `make_shared("Me")`? (OK, maybe this conversion isn't so evil, but still...) – Yehezkel B. Oct 22 '16 at 20:20
  • @Yehezkel B. I am not arguing in favor of this style. Long story short, I was doing some benchmarks and I wanted to define a somewhat longer tree object in C++ in the same style I was doing it in Python, with nested lists and dictionaries. With a large nested object `make_shared` makes it so much less readable. – Andrei Oct 23 '16 at 21:19