C ++ Type-erase function template using lambdas

I am trying to introduce erasure of an object and run into some problem, and I hope someone here can have experience.

I didn’t have the type of problem - erasing arbitrary non-templated functions; So far, what I have been doing is creating a custom static "virtual table" -valued collection of function pointers. All this is controlled using unassembled lambdas, as they break up into pointers to a free function:

 template<typename Value, typename Key> class VTable { Value (*)(const void*, const Key&) at_function_ptr = nullptr; // ... template<typename T> static void build_vtable( VTable* table ) { // normalizes function into a simple 'Value (*)(const void*, const Key&)'' type static const auto at_function = []( const void* p, const Key& key ) { return static_cast<const T*>(p)->at(key); } // ... table->at_function_ptr = +at_function; } // ... } 

(There are more helper functions / aliases that are omitted for brevity)

Unfortunately, this same approach does not work with the template function.

My desire is for the erasable type class to have something similar to the following:

 template<typename U> U convert( const void* ptr ) { return cast<U>( static_cast<const T*>( ptr ) ); } 

Where:

  • cast is a free function,
  • U is the type that is being called,
  • T is the type of erasable type from which it is called, and
  • ptr is a font-erasable pointer that follows the same idiom indicated above to erase a type.

[Edit: problem above: T not known from convert function; the only function that knows the type T in this example is build_vtable . This may require a design change.]

The reason this has become a difficult task is because there is no easy way to enter erase both types independently. The classic / idiomatic technique of erasing the base class does not work here, since you cannot a virtual template . I experimented with a visitor-like sample, with little success causing this.

Does anyone who has the experience of erasing styles have any suggestions or methods that can be used to achieve I'm trying to do this? Preferably in the standard C ++ code 14. Or maybe there are design changes that could facilitate the same concept that is needed here?

I have been looking for this answer for a while and have not had much luck. There are several cases that are similar to what I am trying to do, but often with sufficient differences that the solutions do not seem to apply to the same problem (please let me know if I am wrong!).

It seems that most readings / blogs on these topics tend to cover the basic technique of erasing styles, but not what I'm looking for here.

Thanks!

Note: please do not recommend boost. I am in an environment where I cannot use their libraries, and do not want to introduce this dependency to the code base.

+7
c ++ templates type-erasure c ++ 14
source share
1 answer

Each individual convert<U> is an erasure of a certain type.

You can enter the deletion of the list of such functions, saving the way it is executed in each case. Suppose you have Us... , type erase all convert<Us>...

If Us... is short, it's easy.

If it is long, it is pain.

It is possible that most of them can be null (since the operation is illegal), so you can implement a sparse vtable that takes this into account, so your vtable is not large and full of zeros. This can be done by erasing the function (using the standard vtable method), which returns a link (or erased using accessories) to the specified sparse vtable that maps from std::typeindex to the converter of the U-layout-converter constructor (which writes to void* in the signature). Then you run this function, retrieve the record, create a buffer to store U, call the U-allocator-constructor converter passing in this buffer.

All this happens in your type_erased_convert<U> function (which itself is not erased), so end users do not need to worry about the internal details.

You know, simple.

The limitation is that the list of possible types of U transforms that are supported must be located before the type is erased. Personally, I would limit type_erased_convert<U> to being called only on the same list of U types, and admit that this list should be basically short.


Or you could create some other conversion schedule that allows you to connect a type to it and determine how to achieve another type, perhaps through some kind of common intermediary.

Or you can use a scripting or bytecode language that enables the full compiler during the execution phase, allowing the type erase method to be compiled against the new completely independent type when called.


 std::function< void(void const*, void*) > constructor; std::function< constructor( std::typeindex ) > ctor_map; template<class...Us> struct type_list {}; using target_types = type_list<int, double, std::string>; template<class T, class U> constructor do_convert( std::false_type ) { return {}; } template<class T, class U> constructor do_convert( std::true_type ) { return []( void const* tin, void* uout ) { new(uout) U(cast<U>( static_cast<const T*>( ptr ) )); }; } template<class T, class...Us> ctor_map get_ctor_map(std::type_list<Us...>) { std::unordered_map< std::typeindex, constructor > retval; using discard = int[]; (void)discard{0,(void( can_convert<U(T)>{}? (retval[typeid(U)] = do_convert<T,U>( can_convert<U(T)>{} )),0 : 0 ),0)...}; return [retval]( std::typeindex index ) { auto it = retval.find(index); if (it == retval.end()) return {}; return it->second; }; } template<class T> ctor_map get_ctor_map() { return get_ctor_map<T>(target_types); } 

You can replace unordered_map with a compact stack when it is small. Note that std::function in MSVC is limited to approximately 64 bytes?


If you do not need a fixed list of source / dest types, we can separate it.

  • Open the typeindex type stored in the erase type container, and the ability to get on it a void const* that points to it.

  • Create a type type that maps type T to the list of types Us... that supports conversion. Use the technique described above to store these conversion functions on a global map. (Note that this card can be placed in static storage, as you can determine the size of the required buffer, etc. But using static unordered_map easier).

  • Create a second type that maps type U to a list of types Ts... that supports conversion from.

  • In both cases, the convert_construct( T const* src, tag_t<U>, void* dest ) function is called to perform the actual conversion convert_construct( T const* src, tag_t<U>, void* dest ) .

You will start with a set of universal goals type_list<int, std::string, whatever> . A certain type would increase it by getting a new list.

For a type T building its sparse conversion table, we will try to use each type of target. If overload convert_construct not found, the map will not be filled for this case. (Generating compile-time errors for types explicitly added to work with T is an option).

At the other end, when we call type_erased_convert_to<U>( from ) , we look for another table that maps the type U cross typeindex into the converter U(*)(void const* src) . Consult the from- T card received from the erasable type T and to- U received in the wrapper code to find the converter.

Now it does not allow certain types of conversion. For example, the type of T that is converted - from something using the .data() -> U* and .size() -> size_t method, must explicitly indicate each type that it converts from.

The next step will be the adoption of a multi-step conversion. A multi-step conversion is where you teach your T convert to some (many) known types, and we teach U convert from a similar (set) of known types. (The glory of these types is optional, I agree, all you need to know is how to create and destroy them, what kind of storage you need, and a way to match the T and U options to use them as an intermediary.)

This may seem redundant. But the ability to convert to std::int64_t and convert from this to any signed integral type is an example of this (and similarly for uint64_t and unsigned).

Or the ability to convert key-value pairs into a dictionary, and then study this dictionary on the other side to determine if we can convert from it.

As you go this route, you will need to learn free typing systems in different scripting languages ​​and bytecode to understand how they did it.

+5
source share

All Articles