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

时间:2016-10-05 19:47:28

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

我试图键入删除一个对象并遇到一个问题,我希望此处有人可能具有专业知识。

我没有问题类型 - 删除任意非模板化函数;到目前为止,我一直在做的是创建自定义static "虚拟表" -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是类型擦除的指针,它遵循上面用于类型擦除的相同习语。

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

这变得具有挑战性的原因是似乎没有任何简单的方法来键入擦除这两种类型 独立。基础类的经典/惯用类型擦除技术在这里不起作用,因为你不能拥有 一个virtual template函数。我尝试过类似游客的模式,类似的成功很少 原因如上。

任何具有类型擦除经验的人都有任何可用于实现目标的建议或技术 我想做什么?最好是符合标准的c ++ 14代码。 或者,也许是否有设计变更可能会促进这里所需的相同概念?

我现在一直在寻找这个答案,并且没有多少运气。有一些案例类似于我尝试做的事情,但往往有足够的差异,解决方案似乎不适用于同样的问题(请告诉我,如果我&#39; m错!)。

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

谢谢!

注意:请不要推荐Boost。我在一个我无法使用他们的库的环境中,而不是 希望将这种依赖性引入代码库。

1 个答案:

答案 0 :(得分:5)

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

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

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

如果它很长,这很痛苦。

其中大部分可能为空(因为在操作中是非法的),因此您可以实现将此考虑在内的稀疏vtable,因此您的vtable不大且充满零。这可以通过类型擦除函数(使用标准vtable技术)来完成,该函数将引用(或类型擦除的访问器)返回到从std::typeindex映射到U-placement-constructor转换器的稀疏vtable(写入到签名中的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中的typeindex限制为大约64个字节左右?

如果您不想要固定的source / dest类型列表,我们可以将其解耦。

  • 公开存储在类型擦除容器中的类型的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,我们将尝试每种目标类型。如果找不到T的重载,则不会为该情况填充映射。 (为明确添加的类型生成编译时错误以使用type_erased_convert_to<U>( from )是一种选择)。

另一方面,当我们调用U时,我们会查找不同的表,将typeindex交叉U(*)(void const* src)类型映射到{{ 1}}转换器。从 - T地图获取的类型被删除T和 - U得到的包裹代码都可以查找转换器。

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

下一步是承认多步转换。多步转换是指教T转换为某些(一组)着名类型的转换,我们教U转换 - 来自类似(一组)着名类型。 (我承认,这些类型的名声是可选的;你需要知道的是如何创建和销毁它们,你需要什么样的存储空间,以及匹配T - 和{{1 - 从选项中,将它们用作中介。)

这可能看起来过于设计。但转换为U并将其转换为任何有符号整数类型的能力就是一个例子(同样适用于std::int64_t和无符号)。

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

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