C++使用lambdas对函数模板进行类型擦除
C++ Type-erasure of a function template using lambdas
我试图键入擦除对象,遇到了一个问题,我希望这里的人可能有专业知识。
我从来没有遇到过擦除任意非模板函数的问题;到目前为止,我一直在做的是创建一个自定义的static
"虚拟表"式的函数指针集合。这一切都是通过非捕获Lambda来管理的,因为它们衰减为自由函数指针:
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;
}
// ...
}
(为了简洁起见,省略了更多辅助函数/别名(
遗憾的是,同样的方法不适用于函数template
。
我希望类型擦除类有类似于以下的东西:
template<typename U>
U convert( const void* ptr )
{
return cast<U>( static_cast<const T*>( ptr ) );
}
其中:
- CCD_ 3是自由函数
- CCD_ 4是被广播到的类型
T
是从中广播的底层类型擦除类型,并且ptr
是类型擦除指针,遵循与上面的类型擦除相同的习惯用法
[编辑:上面的问题是从函数convert
中不知道T
;本例中唯一知道T
类型的函数是build_vtable
。这可能只需要更改设计]
这变得具有挑战性的原因是,似乎没有任何简单的方法来键入擦除这两种类型独立地。基类的经典/惯用类型擦除技术在这里不起作用,因为您不能11CCD_ 12功能。我尝试过类似访问者的模式,但在类似的模式中收效甚微以上原因。
有打字擦除经验的人有什么建议或技术可以用来实现我想做什么?最好采用符合c++14规范的标准。或者,也许有没有设计上的改变可以促进这里想要的相同概念?
我已经四处寻找这个答案有一段时间了,运气不太好。有一些案例与我试图做的类似,但往往有足够的差异,以至于解决方案似乎不适用于同一个问题(如果我错了,请告诉我!(。
似乎大多数关于这些主题的阅读/博客都倾向于涵盖基本类型的擦除技术,但不是我在这里寻找的!
谢谢!
注意:请不要推荐Boost。我所处的环境无法使用他们的图书馆希望将这种依赖性引入到代码库中
每个不同的convert<U>
都是不同类型的擦除。
您可以键入擦除此类函数的列表,存储在每种情况下执行该操作的方法。所以假设您有Us...
,键入擦除所有convert<Us>...
。
如果Us...
很短,这很容易。
如果它很长,这是一种痛苦。
其中大多数可能是空的(因为在操作中是非法的(,所以你可以实现一个考虑到这一点的稀疏vtable,这样你的vtable就不会很大,也不会满是零。这可以通过类型擦除函数(使用标准vtable技术(来实现,该函数返回对所述稀疏vtable的引用(或类型擦除访问器(,该稀疏vtable从std::typeindex
映射到U-placement-constructor转换器(写入签名中的void*
(。然后运行该函数,提取条目,创建一个缓冲区来存储U,调用传入该缓冲区的U-placement-constructor转换器。
这一切都发生在type_erased_convert<U>
函数中(它本身并没有被类型擦除(,所以最终用户不必关心内部细节。
你知道,很简单。
限制是,支持的可能转换为类型U
的列表需要位于类型擦除的位置之前。就我个人而言,我会将type_erased_convert<U>
限制为仅在U
类型的同一列表上调用,并接受这个列表必须非常短。
或者,您可以创建一些其他的转换图,让您将一个类型插入其中,并确定如何可能通过一些公共中介到达另一个类型。
或者,您可以使用脚本或字节码语言,该语言在执行阶段包括一个完整的编译器,允许在调用时根据一个新的完全独立的类型编译类型擦除方法。
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);
}
当unordered_map
很小时,您可以将其替换为紧凑的基于堆栈的CCD_23。请注意,MSVC中的std::function
限制在大约64个字节左右?
如果您不想要一个固定的源/目标类型列表,我们可以将其解耦。
暴露存储在类型擦除容器中的类型的
typeindex
,以及获取指向它的void const*
的能力。创建一个类型trait,将类型
T
映射到它支持转换的类型Us...
的列表。使用上述技术将这些转换函数存储在(全局(映射中。(请注意,此映射可以放置在静态存储中,因为您可以推断所需缓冲区的大小等。但使用static unordered_map
更容易(。创建第二个类型trait,将类型
cast
0映射到支持转换的类型Ts...
的列表。在这两种情况下,都会调用函数
convert_construct( T const* src, tag_t<U>, void* dest )
来进行实际转换。
您可以从一组通用目标type_list<int, std::string, whatever>
开始。一个特定的类型会通过一个新的列表来扩充它。
对于类型T
构建其稀疏转换表,我们将尝试每个目标类型。如果未能找到convert_construct
的过载,则不会为这种情况填充映射。(为显式添加以使用T
的类型生成编译时错误是一个选项(。
另一方面,当我们调用type_erased_convert_to<U>( from )
时,我们会查找一个不同的表,该表将类型U
跨typeindex
映射到U
0转换器。参考从类型擦除的T
得到的from-T
映射和包装代码中得到的to-U
来找到转换器。
现在,这不允许某些类型的转换。例如,使用.data() -> U*
和.size() -> size_t
方法从任何类型转换的类型T
需要显式列出它从中转换的每个类型。
下一步将是允许进行多步骤转换。多步骤转换是指教T
转换为某些(一组(著名类型,我们教U
从类似的(一套(著名类型转换。(我承认,这些类型的名声是可选的;你只需要知道如何创建和销毁它们,你需要什么存储,以及如何匹配T
-to和U
-from选项,将它们用作中介。(
这可能看起来设计过度了。但转换为std::int64_t
并从其转换为任何有符号积分类型的能力就是一个例子(类似于uint64_t
和无符号(。
或者能够转换为键值对的字典,然后在另一边检查这个字典,以确定我们是否可以从中转换。
当你沿着这条路走下去时,你会想要检查各种脚本和字节码语言中的松散类型系统,以了解它们是如何做到的
- 在运行时处理类型擦除的数据-如何不重新发明轮子
- 类型擦除的std::function与虚拟函数调用的开销
- 如何在C++中允许成员函数的自定义返回类型进行类型擦除?
- 如何投射类型擦除的 std::function?
- 类型擦除代码的依赖注入单元测试
- 标准库中是否有与 std::thread 的构造函数语义匹配的类型擦除函数包装器?
- 类型擦除和一种模板方法模式
- 在小对象优化中调试崩溃以进行类型擦除
- 带有模板复制和移动构造函数的C++类型擦除
- C++技术:类型擦除与纯多态性
- 使用类型擦除创建运行时type_traits查询
- 类型擦除是否有效取决于优化级别
- 转换为类型擦除的元组
- 如何实现像 std::function 这样的自毁类型擦除类
- unique_ptr类型擦除析构函数不能正常工作(带有警告)
- 什么是C++中的类型擦除
- C++类型擦除,请使用 std::function 捕获单个类的多个方法
- Boost类型擦除的实践和发现
- 现代C++中的类型擦除分配器
- c++类型擦除/类型封装?发现类型