user6245072 user6245072 - 1 month ago 14
C++ Question

Avoiding void pointers

I am implementing my own programming language in C++11.

One of the datatypes I've designed is the

Token
class. It is meant to store a token read from the source file, with the content of the token, it's type, and the line at which it was encountered.

A token can be either a single-character symbol, a lenghtful string, a number, or a name. So it needs to be able to store different data types, either a character for symbols, a double for numbers, and a std::string for names and strings.

The way I'm implementing this is by storing that value in a void pointer, and by adding an attribute of a custome enum which helps understand what type should you cast that void pointer to.

Of course I could have a class for each of those subtypes of Token, but I would at some point anyway need to store all of them as a
Token*
, which means I will still need to have an enum which helps me know what type should I cast that
Token*
to.

Here's the actual code for it:

enum token_type {
symbol,
number,
name,
string
};

struct Token {
void* value = nullptr;
token_type type;
unsigned int line;

Token(void* new_value, token_type new_type, unsigned int new_line):
value(new_value), type(new_type), line(new_line)
{}

~Token() {
switch (type) {
case symbol:
delete (char*) value;
break;
case number:
delete (double*) value;
break;
case name:
case string:
delete (std::string*) value;
}
}
};


What is a good design pattern to implement this which avoids the use of void pointers and (possibly) enums? Everyone keeps telling me that this design is wrong, but I got no suggestions on how to actually improve this situation, so I asked here.

Answer

You can erase the type as it follows:

class Token {
    using Deleter = void(void*);
    using Func = void(*)(void*);

    template<typename T>
    static void proto(void *ptr) {
        T t = static_cast<T*>(ptr);
        // do whatever you want here...
        // ... or use specializations.
    }

public:
    template<typename T>
    Token(T* value):
        value{value, [](void *ptr) { delete static_cast<T*>(ptr); }},
        func{&proto<T>}
    {}

    void operator()() {
        func(value.get());
    }

private:
    std::unique_ptr<void, Deleter> value;
    Func func;
};

The instance will be correctly deleted for the smart pointer knows the type.
In a similar manner, by means of a bunch of specializations of proto, you can define different operations for the multiple types you want to deal with.

It follows a minimal, working example:

#include <memory>
#include <iostream>

struct A {};
struct B {};

class Token {
    using Deleter = void(*)(void*);
    using Func = void(*)(void*);

    template<typename T>
    static void proto(void *ptr);

public:
    template<typename T>
    Token(T *value): 
        value{value, [](void *ptr) { delete static_cast<T*>(ptr); }},
        func{&proto<T>}
    {}

    void operator()() {
        func(value.get());
    }

private:    
    std::unique_ptr<void, Deleter> value;
    Func func;
};

template<>  
void Token::proto<A>(void *ptr) {
    A *a = static_cast<A*>(ptr);
    // use a
    (void)a;
    std::cout << "A" << std::endl;
}

template<>  
void Token::proto<B>(void *ptr) {
    B *b = static_cast<B*>(ptr);
    // use b
    (void)b;
    std::cout << "B" << std::endl;
}

int main() {
    Token token{new A};
    token();
}