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_item
returns.
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?