Home > front end >  In C , how can i declare a variable with a generic type, and instatiate it later?
In C , how can i declare a variable with a generic type, and instatiate it later?

Time:04-23

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 unions, 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;
}
  •  Tags:  
  • c
  • Related