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?
