pooya13
pooya13

Reputation: 2671

Templated member variables in C++

Often when writing templated code, I find myself needing to store an instance of the template type in a member variable. For example, I might need to cache a value to be used later on. I would like to be able to write my code as:

struct Foo
{
    template<typename T>
    T member;
    
    template<typename T>
    void setMember(T value)
    {
        member<T> = value;
    }

    template<typename T>
    T getMember()
    {
        return member<T>;
    }
};

Where members are specialized as they are used. My question:

  1. Is such templated member variable possible with current C++ generative coding facilities?
  2. If not, are there any proposals for such a language feature?
  3. If not, are there any technical reasons why such a thing is not possible?

It should be obvious that I do not want to list all possible types (e.g. in a std::variant) as that is not generative programming and would not be possible if the user of the library is not the same as the author.

Edit: I think this somewhat answers my 3rd question from above. The reason being that today's compilers are not able to postpone instantiation of objects to after the whole program has been parsed: https://stackoverflow.com/a/27709454/3847255

Upvotes: 0

Views: 2784

Answers (2)

ecatmur
ecatmur

Reputation: 157314

This is possible in the library by combining existing facilities.

The simplest implementation would be

std::unordered_map<std::type_index, std::any>

This is mildly inefficient since it stores each std::type_index object twice (once in the key and once inside each std::any), so a std::unordered_set<std::any> with custom transparent hash and comparator would be more efficient; this would be more work though.

Example.

As you say, the user of the library may not be the same as the author; in particular, the destructor of Foo does not know which types were set, but it must locate those objects and call their destructors, noting that the set of types used may be different between instances of Foo, so this information must be stored in a runtime container within Foo.

If you're wary about the RTTI overhead implied by std::type_index and std::any, we can replace them with lower-level equivalents. For std::type_index you can use a pointer to a static tag variable template instantiation (or any similar facility), and for std::any you can use a type-erased std::unique_ptr<void, void(*)(void*)> where the deleter is a function pointer:

using ErasedPtr = std::unique_ptr<void, void(*)(void*)>;
std::unordered_map<void*, ErasedPtr> member;
struct tag {};
template<class T> inline static tag type_tag;

    member.insert_or_assign(&type_tag<T>, ErasedPtr{new T(value), [](void* p) {
        delete static_cast<T*>(p);
    }});

Example. Note that once you make the deleter of std::unique_ptr a function pointer, it is no longer default-constructible, so we can't use operator[] any more but must use insert_or_assign and find. (Again, we've got the same DRY violation / inefficiency, since the deleter could be used as the key into the map; exploiting this is left as an exercise for the reader.)

Upvotes: 1

Andrey Semashev
Andrey Semashev

Reputation: 10596

Is such templated member variable possible with current C++ generative coding facilities?

No, not exactly what you describe. What is possible is to make the enclosing class a template and use the template parameters to describe the types of the class' members.

template< typename T >
struct Foo
{
    T member;
    
    void setMember(T value)
    {
        member = value;
    }
    
    T getMember()
    {
        return member;
    }
};

In C++14 and later, there are variable templates, but you can't make a template non-static data member of a class.

If not, are there any proposals for such a language feature?

Not that I know of.

If not, are there any technical reasons why such a thing is not possible?

The primary reason is that that would make it impossible to define binary representation of the class. As opposed to templates, a class is a type, which means its representation must be fixed, meaning that at any place in the program Foo and Foo::member must mean the same things - same types, same object sizes and binary layout, and so on. A template, on the other hand, is not a type (or, in case of variable templates, is not an object). It becomes one when it is instantiated, and each template instantiation is a separate type (in case of variable templates - object).

Upvotes: 2

Related Questions