pieter3d
pieter3d

Reputation: 135

How to provide a void* accessor for a templated type?

I have a custom container class that is templated:

template<typename T>
class MyContainer {
  T Get();
  void Put(T data);
};

I would like to pass a pointer to this container to a function that will access the container's data as generic data - i.e. char* or void*. Think serialization. This function is somewhat complicated so it would be nice to not specify it in the header due to the templates.

// Errors of course, no template argument
void DoSomething(MyContainer *container);

I'm ok with requiring users to provide a lambda or subclass or something that performs the conversion. But I can't seem to come up with a clean way of doing this.

I considered avoiding templates altogether by making MyContainer hold a container of some abstract MyData class that has a virtual void Serialize(void *dest) = 0; function. Users would subclass MyData to provide their types and serialization but that seems like it's getting pretty complicated. Also inefficient since it requires storing pointers to MyData to avoid object slicing and MyData is typically pretty small and the container will hold large amounts (a lot of pointer storage and dereferencing).

Upvotes: 1

Views: 125

Answers (3)

Acorn
Acorn

Reputation: 26076

I would like to pass a pointer to this container to a function that will access the container's data as generic data - i.e. char* or void*. Think serialization.

Can't be done in general, because you don't know anything about T. In general, types cannot be handled (e.g. copied, accessed, etc.) as raw blobs through a char * or similar.

Therefore, you would need to restrict what T can be, ideally enforcing it, otherwise never using it for Ts that would trigger undefined behavior. For instance, you may want to assert that std::is_trivially_copyable_v<T> holds. Still, you will have to consider other possible issues when handling data like that, like endianness and packing.

This function is somewhat complicated so it would be nice to not specify it in the header due to the templates.

Not sure what you mean by this. Compilers can handle very easily headers, and in particular huge amounts of template code. As long as you don't reach the levels of e.g. some Boost libraries, your compile times won't explode.

I considered avoiding templates altogether by making MyContainer hold a container of some abstract MyData class that has a virtual void Serialize(void *dest) = 0; function. Users would subclass MyData to provide their types and serialization but that seems like it's getting pretty complicated. Also inefficient since it requires storing pointers to MyData to avoid object slicing and MyData is typically pretty small and the container will hold large amounts (a lot of pointer storage and dereferencing).

In general, if you want a template, do a template. Using dynamic dispatching for this will probably kill performance, specially if you have to go through dispatches for even simple types.


As a final point, I would suggest taking a look at some available serialization libraries to see how they achieved it, not just in terms of performance, but also in terms of easy of use, integration with existing code, etc. For instance, Boost Serialization and Google Protocol Buffers.

Upvotes: 0

n. m. could be an AI
n. m. could be an AI

Reputation: 119877

You don't need any char* or void* or inheritance.

Consider this simplified implementation:

template <class T>
void Serialize (std::ostream& os, const MyContainer<T>& ct) {
  os << ct.Get();
}

Suddenly this works for any T that has a suitable operator<< overload.

What about user types that don't have a suitable operator<< overload? Just tell the users to provide one.

Of course you can use any overloaded function. It doesn't have to be named operator<<. You just need to communicate its name and signature to the users and ask them to overload it.

Upvotes: 1

Dmitry Gordon
Dmitry Gordon

Reputation: 2324

You can introduce a non-template base class for the container with a pure virtual function that returns a pointer to raw data and implement it in your container:

class IDataHolder
{
public:
    virtual ~IDataHolder(); // or you can make destructor protected to forbid deleteing by pointer to base class
    virtual const unsigned char* GetData() const = 0;
};

template<typename T>
class MyContainer : public IDataHolder
{
public:
  T Get();
  void Put(T data);
  const unsigned char* GetData() const override { /* cast here internal data to pointer to byte */}
};

void Serialize(IDataHolder& container)
{
    const auto* data = container.GetData();
    // do the serialization
}

Upvotes: 0

Related Questions