1

In C++ you can use virtual methods to have following code work as you expect:

#include <iostream>
#include <string>

class BaseClass {
public:
    virtual std::string class_name() const { return "Base Class"; }
};

class FirstClass : public BaseClass {
    int value = 1;
public:
    std::string class_name() const { return "FirstClass"; }
};

class SecondClass : public BaseClass {
    long long value = -1;
public:
    std::string class_name() const { return "SecondClass"; }
};

int main() {
    const int array_size = 5;
    const bool in_first_mode = true;

    void *data;
    int sample_size;
    if (in_first_mode) {
        data = new FirstClass[array_size];
        sample_size = sizeof(FirstClass);
    } else {
        data = new SecondClass[array_size];
        sample_size = sizeof(SecondClass);
    }

    // this is class-independent code 
    for (int index = 0; index < array_size; ++index) {
        BaseClass *pointer = static_cast<BaseClass*>(data + index * sample_size);
        std::cout << pointer->class_name() << std::endl;
    }

    return 0;
}

This will work correctly for both in_first_mode = true and in_first_mode = false. So, basically, when you want to write code that works for both classes you can just use pointer to the BaseClass.

But what if you already given data buffer, filled with data of type TypeOne, TypeTwo, TypeThree or TypeFour, and in runtime you know that type, which stored in int type. Problem is that TypeOne, TypeTwo, TypeThree and TypeFour have not inherited from one base class. In my case, actually, they are structs from 3rd party library, which is already compiled C-compatible library, so I can not modify it. I want to get something like pointer from the example above, but problem arises with identifying what C++ type should have this pointer.

It there a more elegant type-casting alternative to making C++ class wrappers to these four types (which gives something similar to the example above), and to making pointer be void * and necessity of

if (type == 1) {
    TypeOne *type_one_pointer = static_cast<TypeOne*>(pointer);
    // do something
} else if (type == 2) {
/* ... */
}

every time I use pointer?

Dmitry
  • 111
  • 1
  • 10
  • 2
    They have different methods? If no - you can use templates. If yes - you should work with them as with different classes. – ForEveR Feb 20 '17 at 11:30
  • Store function pointers in an array and use `type` as an index. – knivil Feb 20 '17 at 11:44
  • @ForEveR, how can I use templates for this? – Dmitry Feb 20 '17 at 11:51
  • @Dmitry I don't know your actual code - so I can't answer you. – ForEveR Feb 20 '17 at 11:52
  • @knivil, my point is not to dublicate code which is absolutely similar besides `pointer` type. If I understood you correctly, you suggest writing different but similar functions for every of these types, which does not eliminate dublicating. – Dmitry Feb 20 '17 at 11:56
  • My answer does not imply code duplication. – knivil Feb 20 '17 at 12:48
  • Adding to a `void*` pointer is not legal. You should first cast to `char*`. – aschepler Feb 20 '17 at 12:53
  • what aschepler said, but then explained: http://stackoverflow.com/questions/3377977/void-pointer-arithmetic You should also read the note _"if you are doing this sort of stuff in C++, usually there is a much better solution. Unless you are an expert and you already ruled out every other alternative, I suggest you post a new question asking if there is a better way to do what you're trying to do!"_ – JHBonarius Feb 20 '17 at 14:12

2 Answers2

2

If the classes are unrelated, you can store them in a std::variant (or use Boost.Variant if your compiler is not C++17 compliant) and access the value with a visitor. This is more flexible than templates, as it allows you to include types with a different interface in the variant type.

For example (I did not compile this code):

#include <iostream>
#include <string>
#include <variant>
#include <vector>

struct TypeOne {
    std::string class_name() const { return "Type one"; }
};

struct TypeTwo {
    int value = 1;
    std::string class_name() const { return "Type two"; }
};

struct TypeThree {
    long long value = -1;
    // note the different function signature
    static std::string class_name() { return "Type three"; }
};

struct TypeFour {
    std::string getMyClassName() const { return "Type four"; }
};

struct Visitor {
    template <class T>
    void operator ()(T&& value) const {
        std::cout << value.class_name() << std::endl;
    }

    // special case        
    void operator ()(const TypeFour& value) const {
        std::cout << value.getMyClassName() << std::endl;
    }
};

int main() {
    typedef std::variant<TypeOne, TypeTwo, TypeThree, TypeFour> Variant;
    std::vector<Variant> values;
    values.emplace_back(TypeOne{});
    values.emplace_back(TypeTwo{});
    values.emplace_back(TypeThree{});
    values.emplace_back(TypeFour{});

    for (const auto& var : values) {
        std::visit(Visitor{}, var);
    }
}
D Drmmr
  • 1,223
  • 8
  • 15
  • But I think OP wants `std::variant` – Jarod42 Feb 20 '17 at 13:17
  • This will be exactly I wanted - an elegant solution for this problem. When these features will land on compilers (GCC 7, Clang 4.0, MSVC 2017 - all To Be Released soon). For now i will use template solution because I don't want to have boost only for this. – Dmitry Feb 20 '17 at 20:13
0

Thanks to @ForEveR, I find the solution. I need to use templates.

It means that if in the example above FirstClass and SecondClass would have no BaseClass one can do so:

#include <iostream>
#include <string>

class FirstClass {
    int value = 1;
public:
    std::string class_name() const { return "FirstClass"; }
};

class SecondClass {
    long long value = -1;
public:
    std::string class_name() const { return "SecondClass"; }
};

template <typename T>
void do_my_stuff(void* void_pointer) {
    T *pointer = static_cast<T*>(void_pointer);
    std::cout << pointer->class_name() << std::endl;
}

int main() {
    const int array_size = 5;
    const bool in_first_mode = true;

    void *data;
    int sample_size;
    if (in_first_mode) {
        data = new FirstClass[array_size];
        sample_size = sizeof(FirstClass);
    } else {
        data = new SecondClass[array_size];
        sample_size = sizeof(SecondClass);
    }

    for (int index = 0; index < array_size; ++index) {
        if (in_first_mode) {
            do_my_stuff<FirstClass>(data + index * sample_size);
        } else {
            do_my_stuff<SecondClass>(data + index * sample_size);
        }
    }

    return 0;
}
Dmitry
  • 111
  • 1
  • 10