List of template classes of different types
Asked Answered
M

7

6

I'm trying to make a list of template classes of variable types. So the idea is to loop of a list of objects that all have a common function, e.g. getValue, but a different type. The type could be any type, raw types or objects.

I need this because i want to have a class that has a list of attributes of different types that i want to be able to construct at runtime.

So my class would look something like:

class MyClass {
    std::list<Attribute<?>*> attributes;
};

And my attribute template:

template<typename T>
class Attribute {
public:
    Test(const T &t) : _t(t) {}

    T getValue() const { return _t; }
    void setValue(const T &t) { _t = t; }

private:
    T _t;
};

int main() {
    MyClass myClass;
    myClass.attributes.push_back(new Attribute<int>(42));
    myClass.attributes.push_back(new Attribute<double>(42.0));
}

As you can see the list of MyClass i put ? because that is my problem. I dont know how to make a list that will take different types of my Attribute template, i.e. int, double etc.

std::list<Attribute<?> *> attributes;

In Java, generics can be used for that. Is it possible in C++ to do this with somekind of construction? I tried using variadic templates but that doesnt seem to help solving my problem.

I need this but not in Java, in C++:

public class GenericAttribute<T> {

    private T value;

    public GenericAttribute (T value) {
        setValue(value);
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

public static void main(String[] args) {

    class Custom {
        public Custom() {}

        @Override public String toString() {
            return "My custom object";
        }
    }

    List<GenericAttribute<?>> attributes = new ArrayList<GenericAttribute<?>>();
    attributes.add(new GenericAttribute<Integer>(1));
    attributes.add(new GenericAttribute<Double>(3.1415926535));
    attributes.add(new GenericAttribute<Custom>(new Custom()));

    for (GenericAttribute<?> attr : attributes) {
        System.out.println(attr.getValue());
    }
}

Output:

1
3.1415926535
My custom object

Thanks for the help!

Mccaskill answered 3/8, 2014 at 18:33 Comment(15)
Test<int> and Test<double> are distinct types and as different as int and double. You need to either give them a common base, or apply some sort of type erasure.Heavyarmed
@Heavyarmed I thought about a common base, but then i lose the ability to call my getValue() const. The idea is to parse over the list and call getValue. Type erasure however i dont know what that is, i have to look into it.Mccaskill
Could you perhaps elaborate a little on why you need this? Right now you have a solution you want to use, but don't tell us what problem the solution is trying to solve. Read about the X-Y problem.Humiliation
@JoachimPileborg i added some explanations at the top of the question, i hope it's a little clearer. Let me know if not.Mccaskill
I edited and added some more info, hope it's a little clearer what the problem is.Mccaskill
Do you can use pointers? In this case, you can use a void* as template argumentUnion
The problem is the entire point is unworkable. You can't simply call getValue functions with differing return types because you don't know the return type. The fact that you can't store these types is just a hint of a bigger problem.Dismiss
@user3755692 Yes i use pointers. I'll give it a try.Mccaskill
@MooingDuck In java it's perfectly possible, so i thought that in C++ it should also be possible somehow.Mccaskill
@fonZ: In Java, it's easy to do what you want, though difficult to use it correctly, because of the type-problem. In C++ types are more important, so it's harder to do what you want. Your best bet is going to be boost::variadic or boost::any or a type-erased interface.Dismiss
Are the attributes dynamic or known at compile time ?Gentlemanfarmer
@Gentlemanfarmer They are known at compile time. But there might be new attributes in the future, with custom classes, that will be compiled later. So the idea is to offer a way to make a class with attributes of which the type can also be a custom class that is made by a developer using the library.Mccaskill
@fonZ, what do you want to do with the Attributes? If you have no clear purpose for them, please say as our answers may differ.Tremulous
@Tremulous Attribute has a getValue and a setValue. Basically it's a class that should contain a getter, setter and the value.Mccaskill
I accepted firda's answer because it was exactly what i needed. Btw thanks for the downvotes whoever it was.Mccaskill
M
11

Version 3: Very Advanced (do not try that at home :D)

class Attribute {
private:
    struct Head {
        virtual ~Head() {}
        virtual void *copy() = 0;
        const type_info& type;
        Head(const type_info& type): type(type) {}
        void *data() { return this + 1; }
    };
    template <class T> struct THead: public Head {
        THead(): Head(typeid(T)) {}
        virtual ~THead() override { ((T*)data())->~T(); }
        virtual void *copy() override {
            return new(new(malloc(sizeof(Head) + sizeof(T)))
                THead() + 1) T(*(const T*)data()); }
    };
    void *data;
    Head *head() const { return (Head*)data - 1; }
    void *copy() const { return data ? head()->copy() : nullptr; }
public:
    Attribute(): data(nullptr) {}
    Attribute(const Attribute& src): data(src.copy()) {}
    Attribute(Attribute&& src): data(src.data) { src.data = nullptr; }
    template <class T> Attribute(const T& src): data(
      new(new(malloc(sizeof(Head) + sizeof(T))) THead<T>() + 1) T(src)) {}
    ~Attribute() {
        if(!data) return;
        Head* head = this->head();
        head->~Head(); free(head); }
    bool empty() const {
        return data == nullptr; }
    const type_info& type() const {
        assert(data);
        return ((Head*)data - 1)->type; }
    template <class T>
      T& value() {
        if (!data || type() != typeid(T))
            throw bad_cast();
        return *(T*)data; }
    template <class T>
      const T& value() const {
        if (!data || type() != typeid(T))
            throw bad_cast();
        return *(T*)data; }
    template <class T>
      void setValue(const T& it) {
        if(!data)
            data = new(new(malloc(sizeof(Head) + sizeof(T)))
                THead<T>() + 1) T(it);
        else {
            if (type() != typeid(T)) throw bad_cast();
            *(T*)data = it; }}
public:
    static void test_me() {
        vector<Attribute> list;
        list.push_back(Attribute(1));
        list.push_back(3.14);
        list.push_back(string("hello world"));
        list[1].value<double>() = 3.141592;
        list.push_back(Attribute());
        list[3].setValue(1.23f);
        for (auto& a : list) {
            cout << "type = " << a.type().name()
              << " value = ";
            if(a.type() == typeid(int)) cout << a.value<int>();
            else if (a.type() == typeid(double)) cout << a.value<double>();
            else if (a.type() == typeid(string)) cout << a.value<string>();
            else if (a.type() == typeid(float))  cout << a.value<float>();
            cout << endl;
        }
    }
};

Output:

type = i value = 1
type = d value = 3.14159
type = Ss value = hello world
type = f value = 1.23

Explanation:

Attribute contains data pointer, which is initializaed by this strange placement new: new(new(malloc(sizeof(Head) + sizeof(T))) THead<T>() + 1) T(src) which first allocates enough room for the Head (should be 2*sizeof(void*) which should be just fine for any allignment of any architecture) and the type itself, constructs THead<T>() (initializes pointer to virtual method table and type info) and moves the pointer after the head = at the place we want data. The object is then constructed by another placement new using copy-constructor (or move-constructor) T(src). struct Head has two virtual functions - destructor and copy() which is implemented in THead<T> and used in Attribute(const Attribute&) copy-constructor. Finally ~Attribute() destructor calls ~Head() virtual destructor and releases the memory (if data != nullptr).

Version 1: Simple Attribute List

#include <vector>
#include <typeinfo>
#include <iostream>
#include <cstdlib>
#include <new>

using namespace std;

class Attributes {
public:
    typedef pair<const type_info&,void*> value_type;
    typedef vector<value_type> vect;
    typedef vect::const_iterator const_iterator;
    template <class T>
      void add(const T& value) {
        data.push_back(pair<const type_info&,void*>(
          typeid(T), new(malloc(sizeof(T))) T(value))); }
    const_iterator begin() const {
        return data.begin(); }
    const_iterator end() const {
        return data.end(); }
private:
    vect data;
} attrs;

int main() {
    attrs.add(1);
    attrs.add(3.14);
    for (auto a : attrs) {
        cout << a.first.name() << " = ";
        if(a.first == typeid(int))
            cout << *(int*)a.second;
        else if(a.first == typeid(double))
            cout << *(double*)a.second;
        cout << endl;
    }
}

Output:

i = 1
d = 3.14

Version 2 (named attributes):

#include <string>
#include <unordered_map>
#include <typeinfo>
#include <iostream>
#include <cstdlib>
#include <new>

using namespace std;

class Attributes {
public:
    typedef pair<const type_info&,void*> value_type;
    typedef unordered_map<string,value_type> map;
    typedef map::const_iterator const_iterator;
    template <class T>
      bool add(const string& name, const T& value) {
        auto it = data.insert(make_pair(
          name, value_type(typeid(T), nullptr)));
        if (!it.second) return false;
        it.first->second.second = new(malloc(sizeof(T))) T(value);
        return true; }
    template <class T>
      const T& get(const string& name) const {
        auto it = data.at(name);
        if (it.first != typeid(T)) throw bad_cast();
        return *(T*)it.second; }
    const_iterator begin() const {
        return data.begin(); }
    const_iterator end() const {
        return data.end(); }
    void erase(const_iterator it) {
        free(it->second.second);
        data.erase(it); }
    bool remove(const string& name) {
        auto it = data.find(name);
        if (it == data.end()) return false;
        free(it->second.second);
        data.erase(it);
        return true; }
private:
    map data;
} attrs;

int main() {
    attrs.add("one", 1);
    attrs.add("pi", 3.14);
    cout << "pi = " << attrs.get<double>("pi") << endl;
    attrs.remove("pi");
    for (auto a : attrs) {
        cout << a.first << " = ";
        if(a.second.first == typeid(int))
            cout << *(int*)a.second.second;
        else if(a.second.first == typeid(double))
            cout << *(double*)a.second.second;
        cout << endl;
    }
}
Manta answered 3/8, 2014 at 22:15 Comment(14)
Thanks +1. But i still have to know the type in your code. I want to call a common function of the class, i.e. getValue.Mccaskill
and do what with it? in Java, you get object (here you can get pair of type info and universal poineter).Manta
get or set the value. The class attribute should have a getValue and setValue, so if i parse an attribute i want to be able to call these functions.Mccaskill
Java/C# analogy: you get object but have to check the type or cast it - in C# there are is and as operators, Java has instanceof I think. Here you have pair, where it.first is like GetType() in C# and TheType.class in Java.Manta
your getValue and setValue ~ pair.second (get and set as void*)Manta
you should post better example/description/definition of what you want. std::tuple with std::get may as well be the answer for your unclear demands.Manta
Well i added my Java proof-of-concept.Mccaskill
That is exactly what I wrote - my Attributes are your ArrayList, my pair<type_info,void*> is your Attribute<?> and your ranged for-loop matches mine, the only difference is, that System.out.println internally calls. toString(), in C++ you have to check the type to know how to print it.Manta
I'll check it out tomorrow, thanks for the effort. I +1'd you already and if i can make it work like that i will accept your answer.Mccaskill
I like version 3 very much, i will definitely look at it today.Mccaskill
updated version 3: this is universal object that knows how to allocate, construct, copy, destruct and deallocate any type, but this would be headache for less experienced programmers. Was fun for me to write it :DManta
Amazing solution really, im trying to understand it. I dont understand this part: new(new(malloc(sizeof(Head) + sizeof(T))) THead<T>() + 1) T(src)). But this was exactly what i needed, thanks.Mccaskill
This blog post also contains a great example.Highclass
Version 2 works also if key is an enum and value a templated base class.Pase
U
1

As you already pointed out, in java it is much easier to do so because all classes extends java.lang.Object. In C(++), there is a similar way, but only for pointers - you can use void* for this. Your list will look something like this then:

std::list<Attribute<void*> *> attributes;

Sure, a void* doesn't save the type. If you need, add this field to your Attribute-class:

public:
    std::type_info type;

Then, if you create an instance of Attributre, do this (probably from the constructor):

type = typeinfo(type_to_store);

Of corse, if you do so from the constructor, you'll need to run typeinfo in the code that calls the constructor.

Then, you can get the name of the class back from that field and the instance back from your void*:

std::string name = attribute->type_info.name();
void * instance  = attribute->getValue();
Union answered 3/8, 2014 at 19:5 Comment(3)
Thanks for the answer, however that partially solves the problem. Like this i can store the list, but i still have to cast them and i dont know the type. +1Mccaskill
@Mccaskill Please read the edit, i've added a short description of RTTIUnion
Thanks im checking it out.Mccaskill
T
1

Take a look at variant - this is a class that can be one of a number of different types, but you don't mind which until you need to operate on the values, in which case you can use the visitor pattern to visit all the types.

It is effectively a C++ type-aware version of the C 'union' construct but as it 'knows' which type was set, it can offer type safety.

The biggest issue with variants is that if you expose your implementation and allow any client to put pretty much any type into your variant (attributes.push_back(new Attribute<Widget>(myWidget));), you're going to be unable to do anything with it. E.g. if you want to do 'sum' on all the values put into your attributes, you'd need them to be convertible to a numeric representation and a Widget might not be.

The bigger question is what are you trying to do with them once you've captured these items as Attributes? Enumerating through them calling getValue() is going to give you different results depending on what types you put in. A visitor object would work, but it's still not clear what value this would bring.

It could be that you need something different, such as an interface, e.g. IAttribute that abstracts the underlying type as long as it conforms to the interface which has a getValueAsDouble() method or getValueAsString() method, which you could do to any type that got passes in - no need for variant or visitor in this case.

Tremulous answered 3/8, 2014 at 20:5 Comment(2)
Thanks and yea i need a variant. Like QVariant in Qt or boost.Variant but i cant use external libraries. And i was trying to do what you can in Java, but in c++ i guess it's impossible. +1Mccaskill
No, you cannot do everything in Java. You add object in Java, you can add pointers in C++. You have to check what type of object you have in there in Java, you have to do the same in C++. No big difference, Java is no stronger.Manta
G
1

As Attribute<int> is different type than Attribute<double>, you can't use list or vector without creating a common base type.

Alternatively, you may store different type into a std::tuple.

Following may help:

template <typename ... Ts>
class MyClass {
public:
    MyClass(const Ts&... args) : attributes(args...) {}
private:
    std::tuple<Attribute<Ts>...> attributes;
};

int main()
{
    MyClass<int, double> myClass(42, 42.0);
    return 0;
}
Gentlemanfarmer answered 3/8, 2014 at 21:11 Comment(0)
C
0

What operations do you want to perform on this collection? Do you want to, say, call getValue on all of the Attribute<int> instances and ignore the others? In that case, a base class is fine—you just don’t make the getValue member function virtual (because its type depends on the subclass) and use RTTI to recover the type information at runtime:

struct AnyAttribute {
  // Needs at least one virtual function to support dynamic_cast.
  virtual ~AnyAttribute() {}
};

template<typename T>
struct Attribute : AnyAttribute { … };

int main() {
  std::vector<AnyAttribute*> attributes;
  attributes.push_back(new Attribute<int>(13));
  attributes.push_back(new Attribute<int>(42));
  attributes.push_back(new Attribute<double>(2.5));

  for (const auto attribute : attributes) {
    if (const auto int_attribute = dynamic_cast<Attribute<int>>(attribute)) {
      std::cout << int_attribute->getValue() << '\n';
    }
  }
}
Crossfertilization answered 3/8, 2014 at 19:42 Comment(7)
Thanks Jon. I tried the base class approach, but i want to call getValue not having to care about the type. In your example i cant just add custom classes that are made later. In this case i want a getValue in the abstract base class. And i dont see how i can do that. +1Mccaskill
@fonZ: I still don’t know what the application of this is. If you don’t know anything about the type of a value, you can’t do anything useful with it. It sounds like you want dynamic typing, and RTTI is one mechanism for just that. Can Attribute<T> exist for any T or is it a fixed set of types?Crossfertilization
Well lets say i want to offer a library that has objects with attributes. I want the developer to be able to construct a class and specify the attributes, which could be of a class the developer made. Such that i would have an object with attributes that i could call the getValue and setValue of.Mccaskill
So it's not a fixed set of types.Mccaskill
I was thinking i might be able to store the type and cast with that somehow ... but i'm not sure how.Mccaskill
Dynamic Cast to discover what random types may have been stuffed into your collection? Ewww! What happens if I put a CElephant, CZebra * and const CBabboon& into it? If you only care about ints, just have a collection of ints.Tremulous
@Tremulous if i know the type, i can cast and use the class. If it follows certain rules, i.e. has some virtual methods that are inherited from a common base class, then i dont see the problem.Mccaskill
C
0

An obvious, but naive solution will be:

  • inherit Attribute<T> from base AttributeBase
  • store AttributeBase (smart-)pointers in container (downcast)
  • when reading container element, somehow figure out its type (RTTI)
  • cast back to derived Attribute<T> (upcast)

You can beautify this ugly solution by adding another level of indirection: make a generic container, that stores generic containers for each attribute, so casting will happen under the hood.

You can use integrated to language RTTI features, such as type_info but as far as I know, it's reliability is questionable. Better solution will be to wrap up some kind of static unique id to each Attribute<T> class and put an accessor to AttributeBase to retrieve it. You can add a typedef to relevant Attribute<T> to your unique id class to make casting easier.

So far so good, but in modern C++, we know, that when you need RTTI, it, probably, means that there is something wrong with your overall code design.

I don't know what exact task you have, but my "gut feelings" say me that you will probably can eliminate need of RTTI by using multiple dispatch (double dispatch), such as Visitor pattern in your code (they always say that when see RTTI).

Also, check some other tricks for inspiration: More C++ Idioms/Coercion by Member Template

Confocal answered 3/8, 2014 at 19:53 Comment(0)
E
0

After more than 10 years of this question, I was looking for some C++ code equivalent to the Java code in the question.

As I could not find any, I ended up doing my own version as close as possible to the original Java but in C++17.

Note1: I was not considering performance, etc.

Note2: Yes, you need to know ahead the types you are going to use (using variants=etc).

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

struct Custom 
{ 
    std::string toString() const { return "My custom object"; } 
};

static std::ostream& operator<<(std::ostream& os, const Custom& obj) 
{ return os << obj.toString(); }

int main() 
{
    using GenericAttribute = std::variant<int, double, Custom>;

    std::vector<GenericAttribute> attributes{};
    attributes.push_back(1);
    attributes.push_back(3.1415926535);
    attributes.push_back(Custom{});

    for (auto& attr : attributes) 
        std::visit([](auto& o) { std::cout << o << std::endl; }, attr);

    return 0;
}
Evaleen answered 11/10 at 4:57 Comment(4)
After 10 years, I didn't expect your answer. But now you gave it, will comment. Newer C++ versions make it easier, true. However, you're overcomplicating things. You don't need GenericAttribute in your example. Also, you don't need to create pointers, furthermore it's discouraged to use raw pointers since C++11. std variant can just take objects. Try this: using GenericAttribute = std::variant<int,double,Custom>;Mccaskill
I've made an edit to show you what I mean.Mccaskill
Thank you for your reply, I really appreciate it and helps my "re-learning"! I'm catching up C++ from more than 20 years ago and I have yet a lot to learn from modern C++. I shot an answer because I could not find anything on what I needed and this was pretty close. Now I have an answer that I was looking for and simplified. Thanks again!Evaleen
You're welcome. You made a very typical mistake, which is to use more than you really need. Usually I ask myself, how can I get the same output with less code? The problems that engineers try to solve come very frequently from overcomplicated solutions to a simple problem. And with newer C++ versions, a lot can be simplified.Mccaskill

© 2022 - 2024 — McMap. All rights reserved.