Avoid creating named variables when pointer is required - c++

I have been creating named variables in order to be able to pass their adress to a constructor that expects a pointer, but I want to be able to create them in a constructor or other function and then pass their address to the constructor that expects a pointer.
I am using C++ 20 and I have the following classes:
#include <iostream>
#include <string>
#include <vector>
#include <random>
using std::string, std::cout, std::cin, std::endl, std::vector;
class symbol {
public:
enum symbolKind {
null,
terminal,
sequence,
weighted,
random
};
protected:
symbolKind kind;
public:
virtual string evaluate() const = 0;
symbolKind getKind() {
return kind;
}
};
class nullSymbol : public symbol {
public:
nullSymbol() {
kind = symbol::null;
}
string evaluate() const override {
return "";
}
};
class terminalSymbol : public symbol {
private:
string termString;
public:
terminalSymbol(string pString) {
kind = symbol::terminal;
termString = pPhoneme;
}
string evaluate() const override {
return termString;
}
};
class sequenceSymbol : public symbol {
private:
vector<symbol*> symArray;
public:
sequenceSymbol(vector<symbol*> pArr) {
kind = symbol::sequence;
symArray = pArr;
}
string evaluate() const override {
string retStr = "";
for (symbol* current : symArray) {
retStr += current->evaluate();
}
return retStr;
}
};
class weightedSymbol : public symbol {
private:
float weight;
symbol* subSym;
public:
weightedSymbol(symbol* pSym, float pWeight) {
kind = symbol::weighted;
subSym = pSym;
weight = pWeight;
}
string evaluate() const override {
return subSym->evaluate();
}
float getWeight() {
return weight;
}
};
class randomSymbol : public symbol {
private:
vector<weightedSymbol*> symArray;
public:
randomSymbol(vector<weightedSymbol*> pArr) {
kind = symbol::random;
symArray = pArr;
}
string evaluate() const override {
float sum = 0.0;
for (weightedSymbol* current : symArray) {
sum += current->getWeight();
}
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0.0, sum);
float randomResult = dis(gen);
float prev = 0;
for (weightedSymbol* current : symArray) {
if (randomResult < (prev += current->getWeight())) return current->evaluate();
}
}
};
I have been creating symbols like this:
terminalSymbol term_a("a");
terminalSymbol term_b("b");
sequenceSymbol seq_ab({ &term_a, &term_b});
cout << "ab test: " << seq_ab.evaluate() << endl;
But I would want to be able to do it like this or similar:
sequenceSymbol seq_ab_2({&terminalSymbol("a"), &terminalSymbol("b")});
cout << "ab test 2: " << seq_ab_2.evaluate() << endl;
This creates an error '&' requires l-value in Visual Studio.
This is a pretty simple example, often there are a lot more variables being created than this. In this case, the addresses are being passed to the std::vector<weightedSymbol*>() constructor; it's the same with the weightedSymbol() constructor which also expects a pointer. This should work not only for the constructor (it doesn't even need to work with the constructor itself if there is another way to achieve the same functionality), but I want a way to create heap objects in a function and then return a pointer to them that works in this situation. It might be that I need to change the classes themselves for this to work, they should just provide the same functionality.
In the end, I want to create these symbol objects dynamically based on user input.
I have searched online and tried using a bunch of different things but didn't manage to get the functionality I want working. What would be a good way to implement this? There is probably a common technique/idiom that I can use, if so, please explain it to me in detail so that I can use it in other projects too.

The objects you pass by pointers need to be destroyed somehow. In this snippet they will be destroyed automatically whenever you exit the block:
terminalSymbol term_a("a");
terminalSymbol term_b("b");
sequenceSymbol seq_ab({ &term_a, &term_b});
What should happen if you create objects without a named variable? Your classes never delete the objects that you pass by pointers, so that should be the caller responsibility to manage the lifespan of each object.
One solution for your problem is to wrap the objects into any sort of smart pointers. For example:
class sequenceSymbol : public symbol {
public:
sequenceSymbol(vector<shared_ptr<symbol>> pArr);
};
sequenceSymbol seq_ab_2({
std::make_shared<terminalSymbol>("a"),
std::make_shared<terminalSymbol>("b")
});

Related

how to return a list of objects without destroying them

How to fix the function 'func' so that it returns the objects without being destroyed?
function 'func' must add the objects to a list and return them but be destroyed
The Smoothy abstract class has a purely virtual description method (). DecoratorSmoothy
contains a smoothy, description () and getPret () methods return the description and price
aggregate smoothy.
SmoothyCuFream and SmoothyCuUmbreluta classes add the text “cu crema”
respectively “cu umbreluta” in the description of the smoothy contained. The price of a smoothy that has the cream increases by 2 euro, the one with the umbrella costs an extra 3 euro.
BasicSmoothy class is a smoothy without cream and without umbrella, method
description () returns the name of the smothy
#include <iostream>
#include <vector>
using namespace std;
class Smoothy {
private:
int pret=0;
public:
virtual string descriere() = 0;
int getPret(){
return pret;
}
void setPret(int a) {
pret += a;
}
};
class BasicSmooty : public Smoothy {
private:
string nume;
public:
BasicSmooty(string n) :
nume { n } {}
string descriere() {
return nume;
}
};
class DecoratorSmoothy : public Smoothy {
private:
Smoothy* smooty;
public:
DecoratorSmoothy() = default;
DecoratorSmoothy(Smoothy* n) :
smooty{ n } {}
string descriere() {
return smooty->descriere();
}
int getPret() {
return smooty->getPret();
}
};
class SmootyCuFrisca : public DecoratorSmoothy {
private:
BasicSmooty bsc;
public:
SmootyCuFrisca(string desc) :
bsc{ desc } {}
string descriere() {
setPret(2);
return bsc.descriere() + " cu frisca ";
}
};
class SmootyCuUmbreluta : public DecoratorSmoothy{
private:
BasicSmooty bsc;
public:
SmootyCuUmbreluta(string desc) :
bsc{ desc } {}
string descriere() {
setPret(3);
return bsc.descriere() + " cu umbreluta ";
}
~SmootyCuUmbreluta() {
cout << "rip";
}
};
vector<Smoothy*> func(void)
{
std::vector<Smoothy*> l;
SmootyCuFrisca a1{ "smooty de kivi" };
SmootyCuUmbreluta a2{ "smooty de kivi" };
SmootyCuFrisca a3{ "smooty de capsuni" };
BasicSmooty a4{ "smooty simplu de kivi" };
l.push_back(&a1);
l.push_back(&a2);
l.push_back(&a3);
l.push_back(&a4);
return l;
}
int main() {
vector<Smoothy*> list;
// Here when i call func() objects are distroyed
list = func();
return 0;
}
In func you are storing the address of function local variables in l. So when you return l from the function, all the Smoothy* are now pointing to invalid memory.
To fix this, you can allocate memory for each pointer you add to l, like this:
l.push_back(new Smoothy{a1}); // instead of l.push_back(&a1);
// etc. for a2, a3, ...
To really get away from this problem, consider not using pointers at all. If your design doesn't need it, you can get rid of the pointers, and you'll save yourself a lot of trouble.
Well, when a method returns, of course all local/automatic variables are destroyed. Under the late revision c++ changes, there is the return && modifier, which invokes move semantics, which means for not const local/automatic objects you return, it steals: clones the returned object, making a new object and copying all the primitives and object pointers, then sets the object pointers to null so they cannot be deleted/freed by the destructor. (Note that C free of a null pointer does nothing!) For const, of course, it must deep copy.

std::find return a class that I can't acesses functions

I come from C/C# language and now I'm trying to learn about C++ and his standards functions.
Now, I'm creating a class called IMonsterDead. I will have a std::vector<IMonsterDead*> with N monsters.
Example:
class IMonsterDead {
public:
IMonsterDead(int Id)
{
this->_Id = Id;
}
virtual void OnDead() = 0;
int Id() const {
return _Id;
}
private:
int _Id;
};
One class which implements that class:
class MonsterTest : public IMonsterDead {
public:
MonsterTest(int generId)
: IMonsterDead(generId)
{
}
virtual void OnDead()
{
std::cout << "MonsterTesd died" << std::endl;
}
};
Ok, if I access directly everything works fine. But I'm trying to use std::find.
Full program test:
int main()
{
std::vector<IMonsterDead*> monsters;
for (int i = 0; i < 1000; i++)
{
monsters.emplace_back(new MonsterTest(1000 + i));
}
int id = 1033;
std::vector<IMonsterDead*>::iterator result = std::find(monsters.begin(), monsters.end(), [id]( IMonsterDead const* l) {
return l->Id() == id;
});
if (result == monsters.end())
std::cout << "Not found" << std::endl;
else
{
// Here I want to access OnDead function from result
}
return 0;
}
So I need to access OnDead function from result but I can't. Intellisense doesn't show anything for me. The result exists.
How can I access that function? Have another better way to do that?
You need to use std::find_if() instead of std::find(). std::find() is for finding an element with a specific value, so you have to pass it the actual value to find, not a user_defined predicate. std::find_if() is for finding an element based on a predicate.
Either way, if a match is found, dereferencing the returned iterator will give you a IMonsterDead* pointer (more accurately, it will give you a IMonsterDead*& reference-to-pointer). You need to then dereference that pointer in order to access any members, like OnDead().
You are also leaking memory. You are not delete'ing the objects you new. And when dealing with polymorphic types that get deleted via a pointer to a base class, the base class needs a virtual destructor to ensure all derived destructors get called properly.
With that said, you are clearly using C++11 or later (by the fact that you are using vector::emplace_back()), so you should use C++11 features to help you manage your code better:
You should use std::unique_ptr to wrap your monster objects so you don't need to delete them manually.
You should always use the override keyword when overriding a virtual method, to ensure you override it properly. The compiler can catch more syntax errors when using override than without it.
You should use auto whenever you declare a variable that the compiler can deduce its type for you. Especially useful when dealing with templated code.
Try something more like this:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
class IMonsterDead {
public:
IMonsterDead(int Id)
: m_Id(Id)
{
}
virtual ~IMonsterDead() {}
virtual void OnDead() = 0;
int Id() const {
return m_Id;
}
private:
int m_Id;
};
class MonsterTest : public IMonsterDead {
public:
MonsterTest(int generId)
: IMonsterDead(generId)
{
}
void OnDead() override
{
std::cout << "MonsterTest died" << std::endl;
}
};
int main()
{
std::vector<std::unique_ptr<IMonsterDead>> monsters;
for (int i = 0; i < 1000; i++)
{
// using emplace_back() with a raw pointer risks leaking memory
// if the emplacement fails, so push a fully-constructed
// std::unique_ptr instead, to maintain ownership at all times...
monsters.push_back(std::unique_ptr<IMonsterDead>(new MonsterTest(1000 + i)));
// or:
// std::unique_ptr<IMonsterDead> monster(new MonsterTest(1000 + i));
// monsters.push_back(std::move(monster));
// or, if you are using C++14 or later:
// monsters.push_back(std::make_unique<MonsterTest>(1000 + i));
}
int id = 1033;
auto result = std::find_if(monsters.begin(), monsters.end(),
[id](decltype(monsters)::value_type &l) // or: (decltype(*monsters.begin()) l)
{
return (l->Id() == id);
}
// or, if you are using C++14 or later:
// [id](auto &l) { return (l->Id() == id); }
);
if (result == monsters.end())
std::cout << "Not found" << std::endl;
else
{
auto &monster = *result; // monster is 'std::unique_ptr<IMonsterDead>&'
monster->OnDead();
}
return 0;
}
Iterators are an interesting abstraction, in this case to be reduced to pointers.
Either you receive the pointer to the element or you get an invalid end.
You can use it as a pointer: (*result)->func();
You can also use it to create a new variable:
IMonsterDead &m = **result;
m.func();
This should give the same assembly, both possible.

Strange issue when iterating a STL set<CStudent> in C++

I have class CStudent and class CStudentGroup which has one member set<CStudent>. I populate the set of an object from the class CStudentGroup. I want to iterate this set and print via the getter of the CStudent class the points of all the students in the set. I do this by assigning the set to a new one. Then I iterate the set with an iterator it. However the compiler gives an error *the object has type qualifiers that are not compatible with the member function CStudent::getP; object type is const CStudent* I would like to ask how can I do this? Thank you in advance.
#include <iostream>
#include <string>
#include <set>
using namespace std;
class CStudent {
string m_strFN;
int m_iPoints;
public:
void setP(int p) {
m_iPoints = p;
}
void setFN(string f) {
m_strFN = f;
}
int getP() {
return m_iPoints;
}
string getFN() {
return m_strFN;
}
CStudent() {
m_strFN = "123456789";
m_iPoints = 70;
}
CStudent(const CStudent& stud) {
m_strFN = stud.m_strFN;
m_iPoints = stud.m_iPoints;
};
CStudent(int p) {
m_iPoints = p;
}
};
class CStudentGroup {
set<CStudent> m_setStudents;
public:
CStudentGroup(const CStudentGroup& grp) {
m_setStudents = grp.m_setStudents;
};
CStudentGroup(set<CStudent> st) {
m_setStudents = st;
}
CStudentGroup() {
CStudent s1(50), s2, s3(s2);
m_setStudents.insert(s1);
m_setStudents.insert(s2);
m_setStudents.insert(s3);
}
set<CStudent> gets() {
return m_setStudents;
}
};
int main()
{
CStudentGroup group;
set<CStudent> stt = group.gets();
for (set<CStudent>::iterator it = stt.begin(); it != stt.end(); it++) {
cout << it->getP() << endl;
}
}
std::set stores keys as constant value, as a change of a key can be a cause of change to its position in red-black tree (typical std::set implementation).
In other words, your CStudent object are considered const or unchangeable.
It's possible to problem here using std::set::const_iterator as a type of iterator inside the loop in combination with std::set::cbegin() and std::set::cend() calls.
Another possible solution is to use foreach-loop:
for (CStudent const& student : stt)
std::cout << student.getP() << '\n';
Moreover, you would need to change CStudent::getP() declaration to be a constant method.
Objects inside a std::set are always const. That is to protect them, in case you decide you change any key field, the sorting order changes and the set invariant is broken.
So basically the set<CStudent>::iterator is a const_iterator and you get a const CStudent& reference. Since your CStudent::getP is not a const member function, you cannot use it.
Solution, make it const:
int getP() const {
return m_iPoints;
}
Naturally, you want to mark as const any function that does not change the contents of your object, not only the ones std::set requires you to do so. This is sometimes called const-correctness and is always a good practice.

C++ declare derived class object inside of if-else and use it outside

I have a (parent) class named Alma with the (virtual) function Getwidth() and two derived class of Alma, named Birs (with the special function Getheight()) and Citrom (with the special function Getdepth()). I want to declare an object - named Attila - which type is Birs or Citrom depending on a bool. Later, I want to use the common function Getwidth() and also the special functions (depending the bool mentioned).
My (not working) code:
/*...*/
/*Classes*/
class Alma{
public: virtual int Getwidth() = 0;
/*ect...*/
}
class Birs: public Alma{
int Getwidth(){return 1;}
public: int Getheight(){return 2;}
/*ect...*/
}
class Citrom: public Alma{
int Getwidth(){return 3;}
public: int Getdepth(){return 4;}
/*ect...*/
}
/*...*/
/*Using them*/
void Useobjects(){
/*Create object depending on bool*/
if(b00lvar){
Birs Andor();
std::cout<<Andor.Getwidth()<<" "<<Andor.Getheight()<<std::endl;
}else{
Citrom Andor();
std::cout<<Andor.Getwidth()<<" "<<Andor.Getdepth()<<std::endl;
}
/*Using the common part of object*/
std::cout<<Andor.Getwidth()<<std::endl;
/*Using the special part of object*/
if(b00lvar){
std::cout<<Andor.Getheight()<<std::endl;
}else{
std::cout<<Andor.Getdepth()<<std::endl;
}
/*ect...*/
}
This is a classic case of polymorphic object handling. Just make sure you are familiar with that concept as well with pointers and references.
What you need is something looking like:
Alma* Andor;
if(b00lvar){
Andor = new Birs();
std::cout<<Andor->Getwidth()<<" "<<Andor->Getheight()<<std::endl;
}else{
Andor = new Citrom();
std::cout<<Andor->Getwidth()<<" "<<Andor->Getdepth()<<std::endl;
}
Next use dynamic_cast to get back to the derived types and finally of course do not forget to delete the object. But first read about those concepts.
You cannot define a single object whose type is this or that, depending on something else. C++ doesn't work this way. C++ is a statically-typed language. This means that the type of every object is determined at compile time. Other languages, like Perl, or Javascript, are dynamically-typed, where the type of an object is determined at runtime, and a single object can be one thing, at one point, and something else at a different point.
But C++ does not work this way.
To do something like what you're trying to do, you have to refactor the code, and work with the virtual superclass. Something like this:
void UseObject(Alma &andor)
{
/*Using the common part of object*/
std::cout<<andor.Getwidth()<<std::endl;
/*Using the special part of object*/
/* This part is your homework assignment */
}
void Useobjects(){
/*Create object depending on bool*/
if(b00lvar){
Birs andor;
std::cout<<Andor.Getwidth()<<" "<<Andor.Getheight()<<std::endl;
UseObject(andor);
}else{
Citrom andor;
std::cout<<Andor.Getwidth()<<" "<<Andor.Getdepth()<<std::endl;
UseObject(andor);
}
}
Another approach would be to use two pointers, in this case passing two pointers to UseObject(). One of the two pointers will always be a nullptr, and the other one a pointer to the instantiated object, with UseObject() coded to deal with whatever object is passed in.
That's also possible, but will result in ugly code, and if I was an instructor teaching C++, I would mark down anyone who handed in code that did that.
If the type of the object (Alma or Citrom) is decided at the startup, then it's a classic polymorphism, as other answers described:
https://stackoverflow.com/a/36218884/185881
What're you missing from your design is, to name the common ancestor with common behaviors (e.g. Gyumolcs).
If the object should once act as Alma and other times as Citrom, you should implement a single class, which have a flag or enum (ACT_AS_CITROM, ACT_AS_ALMA), or, if the behavior is limited to one method, then it should have a parameter, which tells which action to perform (alma-like or citrom-like).
You can do this with pointer semantic and type introspection with dynamic_cast. I extended your example to show how I would approach it.
Here is the Demo
#include <iostream>
#include <memory>
using namespace std;
class Alma{
public:
virtual int Getwidth() = 0;
};
class Birs: public Alma{
public:
int Getwidth() { return 1; }
int Getheight() { return 2; }
};
class Citrom: public Alma{
public:
int Getwidth() { return 3; }
int Getdepth() { return 4; }
};
shared_ptr<Alma> make_attila(bool birs)
{
if (birs)
return make_shared<Birs>();
else
return make_shared<Citrom>();
}
void test_attila(shared_ptr<Alma> attila)
{
cout << "width: " << attila->Getwidth() << "\n";
if (auto as_birs = dynamic_pointer_cast<Birs>(attila))
cout << "height: " << as_birs->Getheight() << "\n";
else if (auto as_citrom = dynamic_pointer_cast<Citrom>(attila))
cout << "depth: " << as_citrom->Getdepth() << "\n";
}
int main() {
shared_ptr<Alma> attila = make_attila(true);
test_attila(attila);
attila = make_attila(false);
test_attila(attila);
return 0;
}
Next step would be to make make_attila a template function taking the Derived class as a template parameter instead of a bool.
template <class Derived>
shared_ptr<Alma> make_attila()
{
return make_shared<Derived>();
}
Two things:
If you want to use it outside the if, you will have to declare it outside the if.
You need references or pointers for this kind of polymorphism.
unique_ptr<Alma> Andor;
if (b00lvar) {
Andor = make_unique<Birs>();
} else {
Andor = make_unique<Citrom>();
}
std::cout << Andor->Getwidth() << std::endl;
Some other answer suggested using shared_ptr but that's overkill here. 99% of the time unique_ptr is sufficient.
Polymorphism isn't always the way to go if an object is known to be either a B or a C. In this case, a boost::variant is often more succinct.
Having said this, if you want to go down the polymorphic route it's important to remember something that will guide the design.
Polymorphic means runtime polymorphic. I.e. the program cannot know the real type of the object. It also cannot know the full set of possible types the object could be, since another developer could manufacture a type that your module's code knows nothing about. Furthermore, when using the Alma interface, the code should not need to know anything more. Invoking magic such as "I know it'll be a Citrom because the bool is true" is laying the foundations for a code maintenance nightmare a few weeks or months down the line. When done in commercial, production code, it results in expensive and embarrassing bug-hunts. Don't do that.
This argues that all relevant information about any object of type Alma must be available in the Alma interface.
In our case, the relevant information is whether it has the concept of height and/or depth.
In this case, we should probably include these properties in the base interface plus provide functions so that the program can query whether the property is valid before using it.
Here is something like your example written this way:
#include <iostream>
#include <memory>
#include <typeinfo>
#include <string>
#include <exception>
#include <stdexcept>
// separating out these optional properties will help me to reduce clutter in Alma
struct HeightProperty
{
bool hasHeight() const { return impl_hasHeight(); }
int getHeight() const { return impl_getHeight(); }
private:
// provide default implementations
virtual bool impl_hasHeight() const { return false; }
virtual int impl_getHeight() const { throw std::logic_error("getHeight not implemented for this object"); }
};
struct DepthProperty
{
bool hasDepth() const { return impl_hasDepth(); }
int getDepth() const { return impl_getDepth(); }
private:
virtual bool impl_hasDepth() const { return false; }
virtual int impl_getDepth() const { throw std::logic_error("getDepth not implemented for this object"); }
};
class Alma : public HeightProperty, public DepthProperty
{
public:
Alma() = default;
virtual ~Alma() = default;
// note: nonvirtual interface defers to private virtual implementation
// this is industry best practice
int getWidth() const { return impl_getWidth(); }
const std::string& type() const {
return impl_getType();
}
private:
virtual int impl_getWidth() const = 0;
virtual const std::string& impl_getType() const = 0;
};
class Birs: public Alma
{
private:
// implement the mandatory interface
int impl_getWidth() const override { return 1; }
const std::string& impl_getType() const override {
static const std::string type("Birs");
return type;
}
// implement the HeightProperty optional interface
bool impl_hasHeight() const override { return true; }
int impl_getHeight() const override { return 2; }
};
class Citrom: public Alma
{
private:
// implement the mandatory interface
int impl_getWidth() const override { return 3; }
const std::string& impl_getType() const override {
static const std::string type("Citrom");
return type;
}
// implement the DepthProperty optional interface
bool impl_hasDepth() const override { return true; }
int impl_getDepth() const override { return 4; }
};
/*...*/
/*Using them*/
// generate either a Birs or a Citrom, but return the Alma interface
std::unique_ptr<Alma> make_alma(bool borc)
{
if (borc) {
return std::make_unique<Birs>();
}
else {
return std::make_unique<Citrom>();
}
}
void Useobjects()
{
for (bool b : { true, false })
{
std::unique_ptr<Alma> pa = make_alma(b);
std::cout << "this object's typeid name is " << pa->type() << std::endl;
std::cout << "it's width is : " << pa->getWidth() << std::endl;
if(pa->hasHeight()) {
std::cout << "it's height is: " << pa->getHeight() << std::endl;
}
if(pa->hasDepth()) {
std::cout << "it's depth is: " << pa->getDepth() << std::endl;
}
}
}
int main()
{
Useobjects();
return 0;
}
expected output:
this object's typeid name is Birs
it's width is : 1
it's height is: 2
this object's typeid name is Citrom
it's width is : 3
it's depth is: 4

C++: Monitoring multiple value types

I want to write a class that can monitor a bunch of different values for easy debugging. Imagine setting "watches" in a visual debugger. I'm picturing something like this:
struct Foo {
int x = 0;
std::string s = "bar";
};
int main() {
Foo f;
ValueMonitor::watch("number", &f.x);
ValueMonitor::watch("string", &f.s);
for (int i = 0; i < 10; ++i) {
++f.x;
if (i > 5) {
f.s = "new string";
}
// print the current value of the variable with the given key
// these should change as the loop goes on
ValueMonitor::print("number");
ValueMonitor::print("string");
// or
ValueMonitor::printAll();
// obviously this would be unnecessary in this example since I
// have easy access to f, but imagine monitoring different
// values from all over a much larger code base
}
}
Then these could be easily monitored somewhere in the application's GUI or whatever.
However, I don't know how to handle the different types that would be stored in this class. Ideally, I should be able to store anything that has a string representation. I have a few ideas but none of them really seem right:
Store pointers to a superclass that defines a toString function or operator<<, like Java's Object. But this would require me to make wrappers for any primitives I want to monitor.
Something like boost::any or boost::spirit::hold_any. I think any needs to be type casted before I can print it... I guess I could try/catch casting to a bunch of different types, but that would be slow. hold_any requires defined stream operators, which would be perfect... but I can't get it to work with pointers.
Anyone have any ideas?
I found a solution somewhere else. I was pretty blown away, so might as well post it here for future reference. It looks something like this:
class Stringable
{
public:
virtual ~Stringable() {};
virtual std::string str() const = 0;
using Ptr = std::shared_ptr<Stringable>;
};
template <typename T>
class StringableRef : public Stringable
{
private:
T* _ptr;
public:
StringableRef(T& ref)
: _ptr(&ref) {}
virtual ~StringableRef() {}
virtual std::string str() const
{
std::ostringstream ss;
ss << *_ptr;
return ss.str();
}
};
class ValueMonitor
{
private:
static std::map<std::string, Stringable::Ptr> _values;
public:
ValueMonitor() {}
~ValueMonitor() {}
template <typename T>
static void watch(const std::string& label, T& ref)
{
_values[label] = std::make_shared<StringableRef<T>>(ref);
}
static void printAll()
{
for (const auto& valueItr : _values)
{
const String& name = valueItr.first;
const std::shared_ptr<Stringable>& value = valueItr.second;
std::cout << name << ": " << value->str() << std::endl;
}
}
static void clear()
{
_values.clear();
}
};
std::map<std::string, Stringable::Ptr> ValueMonitor::_values;
.
int main()
{
int i = 5;
std::string s = "test"
ValueMonitor::watch("number", i);
ValueMonitor::watch("string", s);
ValueMonitor::printAll();
i = 10;
s = "new string";
ValueMonitor::printAll();
return 0;
}