首页 文章

C使用lambdas对函数模板进行类型擦除

提问于
浏览
8

我正在尝试键入擦除一个对象并遇到一个问题,我希望有人在这里可能有专业知识 .

我没有遇到任何类型的问题 - 擦除任意非模板化函数;到目前为止,我一直在做的是创建一个自定义的 static "virtual table" -esque函数指针集合 . 这都是使用非捕获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 ) );
}

哪里:

  • cast 是一个免费功能,

  • U 是要转换的类型,

  • T 是从中铸造的基础类型擦除类型,和

  • ptr 是类型擦除指针,它遵循上面用于类型擦除的相同习语 .

[编辑:上面的问题是函数 convert 不知道 T ;在示例中唯一知道 T 类型的函数是 build_vtable . 这可能只需要进行设计更改]

这变得具有挑战性的原因是,似乎没有任何简单的方法可以独立地键入两种类型 . 基类的经典/惯用类型擦除技术没有 virtual template 函数 . 由于与上述类似的原因,我尝试了类似访问者的模式,但收效甚微 .

有类型擦除经验的人是否有任何建议或技术可用于实现我想要做的事情?优选地,符合标准的c 14代码 . 或者,也许是否有设计变更可能会促进这里所需的相同概念?

我一直在寻找这个答案一段时间,并没有太多运气 . 有一些情况类似于我正在尝试做的事情,但通常有足够的差异,解决方案似乎不适用于同样的问题(如果我错了,请告诉我!) .

看来这些主题的大多数读物/博客都倾向于涵盖基本的类型擦除技术,但不是我在这里寻找的东西!

谢谢!

Note: 请不要推荐Boost . 我在一个我无法使用他们的库的环境中,并且不希望将该依赖项引入代码库 .

1 回答

  • 5

    每个不同的 convert<U> 是一种独特的类型擦除 .

    您可以键入擦除此类函数的列表,并在每种情况下存储执行此操作的方法 . 所以假设你有 Us... ,键入erase all convert<Us>... .

    如果 Us... 很短,这很容易 .

    如果它很长,这是一个痛苦 .

    它们中的大多数可能为空(因为在操作中是非法的),因此您可以实现将此考虑在内的稀疏vtable,因此您的vtable不大且充满零 . 这可以通过类型擦除函数(使用标准vtable技术)来完成,该函数将引用(或类型擦除的访问器)返回到从 std::typeindex 映射到U-placement-constructor转换器(写入 void* )的稀疏vtable . 签名) . 然后运行该函数,提取条目,创建缓冲区以存储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 . 请注意,MSVC中的 std::function 限制在大约64字节左右?


    如果您不想要固定的源/目录类型列表,我们可以将其解耦 .

    • 公开存储在类型中的 typeindex 类型擦除容器,以及指向它的 void const* 的能力 .

    • 创建一个类型特征,将类型 T 映射到它支持转换为的类型列表 Us... . 使用上述技术将这些转换函数存储在(全局)映射中 . (请注意,此映射可以放在静态存储中,因为您可以推断出所需缓冲区的大小等 . 但使用 static unordered_map 更容易) .

    • 创建第二个类型特征,将类型 U 映射到类型列表 Ts... 它支持conversion-from .

    • 在这两种情况下,都会调用函数 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(*)(void const* src) 转换器 . 来自类型擦除的 T 和来自包装代码中的to- U 的from- T Map 都可以查找转换器 .

    现在,这不允许某些类型的转换 . 例如,从具有 .data() -> U*.size() -> size_t 方法的任何东西转换的类型 T 需要明确列出它转换的每个类型 .

    下一步是承认多步转换 . 你可以通过多步转换来教你的 T 转换成一些(一组)着名类型,我们教 U 转换 - 来自类似(一组)着名类型 . (这些类型的名声是可选的,我承认;你需要知道的是如何创建和销毁它们,你需要什么样的存储,以及一种匹配 T -to和 U -from选项的方法,以便使用他们作为中间人 . )

    这可能看起来过于设计 . 但转换为 std::int64_t 并将其转换为任何有符号整数类型的能力就是一个例子(对于 uint64_t 和无符号)也是如此 .

    或者转换为键值对字典的能力,然后在另一侧检查这个字典以确定我们是否可以转换它 .

    当你沿着这条路走下去时,你会想要用各种脚本和字节码语言检查松散的打字系统,以了解它们是如何做到的 .

相关问题