0

I have the following problem:

  • I have a bunch of specialized classes with a common ancestor.
  • specialized classes are actually specialized from the same template.
  • I have a std::map<std::string, ancestor> where I need to store my instances.

something like:

class generic {
};

template <class T> special : public generic {
}

std::map<std::string, generic> instances;

instances.emplace("int", special<int>());
instances.emplace("bool", special<bool>());
instances.emplace("float", special<float>());

My problem is when I access the instances (e.g.: instances.at("int")) I always find an instance of generic.

Note this works if I use pointers:

std::map<std::string, generic*> instances;

auto a = special<int>();
auto b = special<bool>();
auto c = special<float>();

instances.emplace("int", &a);
instances.emplace("bool", &b);
instances.emplace("float", &c);

... but, of course, pointers become invalid as soon as a, b and c get out of scope and are destroyed.

I tried playing around with std::move() but I couldn't find the right spell.

What I think it happens in the first snippet (no pointers) is emplace actually uses a copy constructor and thus I lose "specialization".

UPDATE: the pointed question What is object slicing? seems to confirm my suspects, but doesn't offer a solution to my problem (or I'm too stupid to understand it).

Question was: "What is the right way to fix this?"

I started with working code using pointers:

#include <iostream>
#include <string>
#include <memory>
#include <map>

class generic {
    std::string _name;

public:
    generic(std::string name) : _name(name) {}

    virtual std::string name() { return _name; }
    virtual std::string value() { return "no value in generic"; }

};

template <class T> class special : public generic {
    T _value;
public:
    special(std::string name, T value) : generic(name), _value(value) {}
    std::string value() override { return std::to_string(_value); }
};

int
main() {
    std::map <std::string, generic*> instances;

    auto a = special<int>("i", 1);
    auto b = special<bool>("b", true);
    auto c = special<float>("f", 3.1415);

    instances.emplace("int", &a);
    instances.emplace("bool", &b);
    instances.emplace("float", &c);

    for (auto i : instances) {
        std::cout << i.first << " -- " << i.second->name() << " -- " << i.second->value() << std::endl;
    }
    return 0;
}

Target is to arrive to completely encapsulate class instantiation and work only through the map; something like:

#include <iostream>
#include <string>
#include <memory>
#include <map>

class generic {
    std::string _name;

public:
    generic(std::string name) : _name(name) {}

    virtual std::string name() { return _name; }
    virtual std::string value() { return "no value in generic"; }

};

template <class T> class special : public generic {
    T _value;
public:
    special(std::string name, T value) : generic(name), _value(value) {}
    std::string value() override { return std::to_string(_value); }
};

template <typename T> void add_item(std::map <std::string, generic*> m, std::string n, T v) {
    auto s = special<T>(typeid(v).name(), v);
    m.emplace(n, &s);
}

int
main() {
    std::map <std::string, generic*> instances;

    add_item<int>(instances, "int", 1);
    add_item<bool>(instances, "bool", true);
    add_item<float>(instances, "float", 3.1415);

    for (auto i : instances) {
        std::cout << i.first << " -- " << i.second->name() << " -- " << i.second->value() << std::endl;
    }
    return 0;
}

which, of course, compiles but is not working because pointers become invalid as soon as add_itemreturns.

Following Richard Critten terse advice and some more help from Paul Sanders I tried modifying my code along the lines:

#include <iostream>
#include <string>
#include <memory>
#include <map>

class generic {
    std::string _name;

public:
    generic(std::string name) : _name(name) {}
    virtual ~generic() = default;

    virtual std::string name() { return _name; }
    virtual std::string value() { return "no value in generic"; }
};

template <class T> class special : public generic {
    T _value;
public:
    special(std::string name, T value) : generic(name), _value(value) {}
    virtual ~special() = default;

    std::string value() override { return std::to_string(_value); }
};

template <typename T> void add_item(std::map <std::string, std::unique_ptr<generic>> &m, const std::string &n, const T &v) {
    m[n] = std::make_unique<special<T>>(typeid(v).name(), v);
    for (auto i : m)
        std::cout << "add_item: " << i.first << " -- " << i.second->name() << " -- " << i.second->value() << std::endl;
}

int
main() {
    std::map <std::string, std::unique_ptr<generic>> instances;

    add_item<int>(instances, "int", 1);
    add_item<bool>(instances, "bool", true);
    add_item<float>(instances, "float", 3.1415);

    for (auto i : instances) {
        std::cout << i.first << " -- " << i.second.get()->name() << " -- " << i.second.get()->value() << std::endl;
    }
    return 0;
}

Unfortunately I seem to be missing something because compilation bombs with "error: use of deleted function".

Can someone be so kind to help me sort this out?

ZioByte
  • 2,690
  • 1
  • 32
  • 68
  • 5
    `std::map instances;` -> `std::map) instances;` See also [What is object slicing?](https://stackoverflow.com/questions/274626/what-is-object-slicing) – Richard Critten Jun 11 '22 at 12:47
  • @RichardCritten: thanks for the hint. the pointed question is mostly useless to me because it explains what I already suspected. My problem is how to **avoid** slicing. I suspect using `unique_ptr` (not cited in "What is object slicing?") is a viable solution. THANKS! – ZioByte Jun 11 '22 at 13:03
  • 1
    @ZioByte It is. In fact, it's **the** solution, pretty much. – Paul Sanders Jun 11 '22 at 14:09
  • @PaulSanders; I strongly suspect you are right, but I'm struggling to fix details. I am updating OP to reflect my tries, but a pointer to some explanation about how to use `unique_ptr` in my case... ;) my c++ skills are more than "a bit" rusty :( – ZioByte Jun 11 '22 at 14:22
  • 1
    You need to allocate something for your `std::unique_ptr`'s to point to. The idiomatic way to do this is using [`std::make_unique`](https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique), [like this](https://wandbox.org/permlink/9DSRBhtmwJkWlr9s), for example. It might also be worth taking a look at [this](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) list of curated books. – Paul Sanders Jun 11 '22 at 14:34
  • @PaulSanders: I updated the OP, can you have a look, please? – ZioByte Jun 11 '22 at 15:01
  • If you have moved beyond the 'object slicing' issue and need help creating a `map` using `std::unique_ptr`, you should ask a new question. Then people can post a proper answer rather than trying to answer in the comments. You have more than enough experience here to know that you can't keep moving the goalposts like this. – Paul Sanders Jun 11 '22 at 15:41
  • I reopened, but now I think it actually should be closed as dupe: https://stackoverflow.com/questions/8237502/iterating-over-a-container-of-unique-ptrs ; Just change your `auto` to `const auto&` to avoid making a copy of `unique_ptr` (which is non-copyable). – pptaszni Jun 23 '22 at 12:35
  • @pptaszni: agreed. That is actually a duplicate. Answer there solves my problem. Should I delete the question or leave it for whoever will be searching in future? – ZioByte Jun 23 '22 at 13:20

0 Answers0