I'm developing a university project where i had to implement a Stack class, one using vectors, the other using lists.
Now i'm creating a menu, where you can choose what kind of data do you want inside (int, float, string) and therefore operating on it.
This is what i did:
cout<<"You chose to implement a Stack using a vector.\n";
cout<<"What data do you want in this Stack?\n";
int data;
cout << "Press:\n- 1 for int\n- 2 for float\n- 3 for string:\n";
cin >> data;
if (data == 1){
lasd::StackVec<int> stack;
} else if (data == 2){
lasd::StackVec<float> stack;
} else if (data == 3) {
lasd::StackVec<float> stack;
}
...
...
My idea was to create 3 different stacks, all with the same name, but different instantiation based on your choice. This way, i don't have to create 3 different sub menus, one for int, one for float, and one for string, since every stack i create will be named 'stack'.. Thing is, this way of course my compiler says that the 'stack variable was not declared in this scope'.
So, i thought i could declare it at the start of this menu, but... i don't really know how to do it. I tried a bit but my syntax was always wrong. I wanted to declare it just as a StackVec, without any particular data inside, and then instatiating it later. Any advices? Maybe my plan wasn't optimal and there are many more efficient way i can follow?
CodePudding user response:
My idea was to create 3 different stacks, all with the same name, but different instantiation based on your choice.
Since you cannot use std::variant
nor union
s, as suggested in the comments,
a possible approach is to have a common non-template base type for your
stack
class. Or, if you don't want to change your hierarchy, you may wrap
your stack
like this:
struct StackWrapper {
virtual ~StackWrapper() = 0;
};
StackWrapper::~StackWrapper() = default;
template <typename T>
struct ConcreteStackWrapper final : public StackWrapper {
~ConcreteStackWrapper() override = default;
T stack{}; // Put your lasd::StackVec<T> instance here
};
Then you have a std::unique_ptr<StackWrapper>
variable that you downcast
based on the value of data
(which encodes your data type) when you need to
operate on your stack. The idea is as follows:
#include <cassert>
#include <cstdio>
#include <memory>
#include <string>
struct StackWrapper {
virtual ~StackWrapper() = 0;
};
StackWrapper::~StackWrapper() = default;
template <typename T>
struct ConcreteStackWrapper final : public StackWrapper {
~ConcreteStackWrapper() override = default;
T stack{}; // Put your lasd::StackVec<T> instance here
};
void print_type(int const&) { std::puts("int"); }
void print_type(std::string const&) { std::puts("std::string"); }
template <typename Func>
void execute(StackWrapper& stackWrapper, int i, Func func) {
switch (i) {
case 1:
func(static_cast<ConcreteStackWrapper<int>&>(stackWrapper).stack);
break;
case 2:
func(static_cast<ConcreteStackWrapper<std::string>&>(stackWrapper).stack);
break;
default:
assert(false);
break;
}
}
int main() {
for (int data = 1; data != 3; data) {
// ^ This is where you would ask for the data type. I'll just loop.
std::unique_ptr<StackWrapper> stack{nullptr};
switch (data) {
case 1:
stack.reset(new ConcreteStackWrapper<int>);
break;
case 2:
stack.reset(new ConcreteStackWrapper<std::string>);
break;
default:
assert(false);
break;
}
execute(*stack, data, [](auto const& stack) { print_type(stack); });
}
}
Output:
int
std::string
I'm not sure how far you can go with this approach. In order to (say) push to
your stack, you have to pass it an int
or a std::string
which most probably
you'll have to read from stdin
.
The only way I can see this working is that you either have a function template like this:
template <typename T>
void interact_with_user(lasd::StackVec<T>&);
that implements your logic in a generic way for T
in { int, float, std::string }
; or you have several functions like these:
void interact_with_user(lasd::StackVec<int>&); // int-specific implementation
void interact_with_user(lasd::StackVec<float>&); // float-specific implementation
void interact_with_user(lasd::StackVec<std::string>&); // std::string-specific implementation
Then, you may actually implement your program like this:
cout<<"You chose to implement a Stack using a vector.\n";
cout<<"What data do you want in this Stack?\n";
int data;
cout << "Press:\n- 1 for int\n- 2 for float\n- 3 for string:\n";
cin >> data;
std::unique_ptr<StackWrapper> stack{nullptr};
switch (data) {
case 1:
stack.reset(new ConcreteStackWrapper<int>);
break;
case 2:
stack.reset(new ConcreteStackWrapper<float>);
break;
case 3:
stack.reset(new ConcreteStackWrapper<std::string>);
break;
default:
assert(false);
break;
}
execute(*stack, data, [](auto const& stack) { interact_with_user(stack); });
In either case, you may move the creation of the stack into
interact_with_user
and have the menu above just call the correct
implementation for the user's choice:
void interact_with_user_int(); // this will create its int stack and operate on it
void interact_with_user_float(); // this will create its float stack and operate on it
void interact_with_user_string(); // this will create its std::string stack and operate on it
cout<<"You chose to implement a Stack using a vector.\n";
cout<<"What data do you want in this Stack?\n";
int data;
cout << "Press:\n- 1 for int\n- 2 for float\n- 3 for string:\n";
cin >> data;
std::unique_ptr<StackWrapper> stack{nullptr};
switch (data) {
case 1:
interact_with_user_int();
break;
case 2:
interact_with_user_float();
break;
case 3:
interact_with_user_string();
break;
default:
assert(false);
break;
}