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:
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
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.
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.)