使用 lambda 对函数模板进行 C++ 类型删除

标签 c++ templates c++14 type-erasure

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

我在类型删除任意非模板化函数时没有遇到问题;到目前为止,我一直在做的是创建一个自定义 static“虚拟表”式的函数指针集合。这一切都由非捕获 lambdas 管理,因为
它们衰减为自由函数指针:

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是类型删除指针,其遵循与上述类型删除相同的习语。

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

    这变得具有挑战性的原因是似乎没有任何简单的方法可以键入删除这两种类型
    独立。基类的经典/惯用类型删除技术在这里不起作用,因为您不能拥有virtual template功能。我已经尝试过类似访客的模式,但在类似的情况下收效甚微
    原因如上。

    有没有在类型删除方面有经验的人有任何建议或技术可以用来实现什么?
    我想做什么?最好使用符合标准的 c++14 代码。
    或者,是否有设计更改可能会促进此处所需的相同概念?

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

    似乎大多数关于这些主题的阅读/博客都倾向于涵盖基本的类型删除技术,但不是我在这里寻找的内容!

    谢谢!

    注:请不要推荐Boost。我处于无法使用他们的库的环境中,并且不
    希望将该依赖项引入代码库。

    最佳答案

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

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

    Us...很短,这很容易。

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

    其中大部分可能为空(因为在操作中是非法的),因此您可以实现一个考虑到这一点的稀疏 vtable,因此您的 vtable 并不大且充满零。这可以通过类型删除函数(使用标准 vtable 技术)来完成,该函数返回对从 std::typeindex 映射的所述稀疏 vtable 的引用(或类型删除访问器)。到 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当它很小时使用基于堆栈的紧凑型。请注意 std::function在 MSVC 中被限制在大约 64 个字节左右?

    如果您不想要源/目标类型的固定列表,我们可以将其解耦。
  • 揭秘typeindex类型删除容器中存储的类型,以及获取 void const* 的能力那指向它。
  • 创建一个映射类型的类型特征 T到类型列表 Us...它支持转换为。使用上述技术将这些转换函数存储在(全局)映射中。 (请注意,此映射可以放置在静态存储中,因为您可以推断所需缓冲区的大小等。但使用 static unordered_map 更容易)。
  • 创建第二个类型特征来映射类型 U到类型列表 Ts...它支持转换自。
  • 在这两种情况下,函数 convert_construct( T const* src, tag_t<U>, void* dest )被调用来进行实际的转换。

  • 您将从一组通用目标开始 type_list<int, std::string, whatever> .特定类型将通过拥有一个新列表来扩充它。

    对于类型 T在构建其稀疏转换表时,我们将尝试每种目标类型。如果 convert_construct 过载如果找不到,则不会为这种情况填充 map 。 (为显式添加以使用 T 的类型生成编译时错误是一个选项)。

    在另一端,当我们拨打 type_erased_convert_to<U>( from ) 时,我们寻找一个不同的表来映射类型 U十字typeindexU(*)(void const* src)转换器。两者都来自- T从类型删除的 map 获得T和到- U在包装代码中得到的被咨询以找到一个转换器。

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

    下一步是承认多步转换。多步转换是您教您的 T 的地方转换为一些(一组)著名类型,我们教 U从相似的(一组)著名类型转换而来。 (这些类型的名气是可选的,我承认;你需要知道的是如何创建和销毁它们,你需要什么样的存储,以及匹配 T -to 和 U -from 的方法选项,将它们用作中介。)

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

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

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

    关于使用 lambda 对函数模板进行 C++ 类型删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39882459/

    相关文章:

    c++ - 可调用的完美转发

    c++ - boost::adaptor::filtered core dumps with boost::range_detail::default_constructible_unary_fn_wrapper "Assertion ` m_impl' 失败”

    c++ - 创建我的第一个程序来显示生日

    C++正则表达式匹配没有标点符号的单词

    c++ - 这是访问列表中对象的正确方法吗?

    c++ - IE9 无法在 BHO 中的 HTMLWindow2 上触发 onscroll 事件

    wpf - 设置 ComboboxItem 的 MaxWidth 以使项目宽度不超过 ComboboxWidth

    c++ - 为什么不允许非静态成员的地址作为模板非类型参数?

    c++ - xcode 关键字语法着色,不识别 c++ std::string

    c++ - 类模板构造函数中引用的未解析外部符号(在 VS 2008 中)