0

(How) is it possible to write a function that instantiates a template class based on input values? For example, given the template class:

template <typename T>
class Container {
  T value;
};

I would need a function like

Container<?> convert(void* value, DataType data_type) {
  switch (data_type) {
    case INT32: {
      Container<int32_t> cnt;
      cnt.value = static_cast<int32_t>(value);
      return cnt;
    }
    ...
  }
}

and use it as follows:

void* value;
DataType data_type; // enum: can be INT32, INT64, ...
do_something(value, &data_type);  // some C function
Container<?> cnt = convert(value, data_type);
Green绿色
  • 1,620
  • 1
  • 16
  • 43
  • 1
    `return std::variant, Container...>` may help. – 康桓瑋 Jan 30 '22 at 08:02
  • If `data_type` is determined at runtime, then so is `Container>`. Either you need a generic type, or you know what type you expect and you can just use the type with the template directly. – super Jan 30 '22 at 08:06
  • @康桓瑋 Thanks, I'll have a look at `std::variant`. @super A solution where the user of my (wrapper) function doesn't have to explicitly specify the data type is not possible, right? – Green绿色 Jan 30 '22 at 08:09

2 Answers2

2

You don't need an enum for this. You can just use the type itself:

template <class T>
Container<To> convert(void* value)
{
    Container<T> cnt{};
    cnt.value = *static_cast<T*>(*value);
    return cnt;
}

with usage:

void* value;
do_something(value, &data_type);  // some C function
Container<int32_t> cnt = convert_to<int_32>(value);

The C function I'm using defines this enum and gives me a data type. Is it right that I cannot get around a solution where the user of my (wrapper) function has to know the return type?

Correct. The value of the enum is known at runtime while the template parameter for Container must be known at compile time. So you cannot determine the type of Container from data_type.

There is something you can do though. You can use std::variant, but I don't go into it here.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • The C function I'm using defines this enum and gives me a data type. Is it right that I cannot get around a solution where the user of my (wrapper) function has to know the return type? – Green绿色 Jan 30 '22 at 08:08
  • @Green绿色 You are speaking of a C function here, but said nothing of it in the question. IMHO it is a major element of context to understand what your real problem is, and you really should add that to the question itself, along with some explaination of what are the C and C++ parts. BTW the way to go is probably a union or a std::variant which is a nicer encapsulation – Serge Ballesta Jan 30 '22 at 08:33
  • @Green绿色 see edit. – bolov Jan 30 '22 at 08:35
  • Uhm, shouldn't your function `convert`/`convert_to` have a return statement? – MatG Jan 30 '22 at 11:50
1

To achieve this you have to map an enum with a type. Instantiation implies this to happen at runtime, which requires some kind of RTTI, be it built in or self made (e.g using the typeid operator, or a std::type_info::hash_code based on that).

If you say enum this implies a finite, known set of to be expected types. The easiest way is to have a non templated base class from which the templated ones inherit.

class Container_base {};
template <typename T>
class Container : public Container_base {
  T value;
};

Your factory function convert can then cast, or instantiate dynamically base class pointers.

Container_base* convert(void* value, DataType data_type) {
  switch (data_type) {
    case INT32: {
      return new Container<int32_t>; // instantiate new object (some people prefer smart pointers, which may handle ownership stuff like deletion for you, with cost of overhead)
      // return reinterpret_cast<Container<int32_t>*>(value); // alternatively just cast/convert type
      break;
    }
    ...
  }
}

Another implementation might be a mapping table instead of switch (slow) using std::unordered_map, depending on the expected amount of types / likelyhood of types in order, etc.

For usage you might either use the C++ virtual feature, use CRTP, or implement functionality directly in the template class - matter of favor and use case.

The above approach allows casting and instatiating. Depending on use case, duck typing might be an enhancement.

Unfortunately C++ does not (yet?) provide us type reflection at runtime.

Memory management consideration

Instead of dealing with dynamic memory management using raw pointers (as in my example above) one might prefer the use of smart pointers (What is a smart pointer and when should I use one?). TLDR: smart pointers manage ownership and sharing behavior for you. They delete dynamically created memory for you without the need to care about.

Your example using smart pointers would look like this

std::shared_ptr<Container_base> convert(void* value, DataType data_type) {
  switch (data_type) {
    case INT32: {
      return std::make_shared<Container<int32_t>>();
      break;
    }
    ...
  }
}
Chris G.
  • 816
  • 2
  • 15