类型擦除类型擦除,“有什么”问题吗?

2023-11-29

所以,假设我想使用类型擦除来键入擦除。

我可以为变体创建伪方法,以实现自然的:

pseudo_method print = [](auto&& self, auto&& os){ os << self; };

std::variant<A,B,C> var = // create a variant of type A B or C

(var->*print)(std::cout); // print it out without knowing what it is

我的问题是,如何将其扩展到std::any?

它不能“原始”地完成。但在我们分配/构造一个std::any我们有我们需要的类型信息。

因此,理论上,增强型any:

template<class...OperationsToTypeErase>
struct super_any {
  std::any data;
  // or some transformation of OperationsToTypeErase?
  std::tuple<OperationsToTypeErase...> operations;
  // ?? what for ctor/assign/etc?
};

可以以某种方式自动重新绑定某些代码,以便上述类型的语法可以工作。

理想情况下,它的使用应与变体情况一样简洁。

template<class...Ops, class Op,
  // SFINAE filter that an op matches:
  std::enable_if_t< std::disjunction< std::is_same<Ops, Op>... >{}, int>* =nullptr
>
decltype(auto) operator->*( super_any<Ops...>& a, any_method<Op> ) {
  return std::get<Op>(a.operations)(a.data);
}

现在我可以把它保留在type,但合理地使用 lambda 语法来使事情变得简单?

理想情况下我想要:

any_method<void(std::ostream&)> print =
  [](auto&& self, auto&& os){ os << self; };

using printable_any = make_super_any<&print>;

printable_any bob = 7; // sets up the printing data attached to the any

int main() {
  (bob->*print)(std::cout); // prints 7
  bob = 3.14159;
  (bob->*print)(std::cout); // prints 3.14159
}

或类似的语法。这不可能吗?不可行吗?简单的?


这是一个使用 C++14 的解决方案boost::any,因为我没有 C++17 编译器。

我们最终得到的语法是:

const auto print =
  make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });

super_any<decltype(print)> a = 7;

(a->*print)(std::cout);

这几乎是最佳的。经过我认为简单的 C++17 更改后,它应该如下所示:

constexpr any_method<void(std::ostream&)> print =
  [](auto&& p, std::ostream& t){ t << p << "\n"; };

super_any<&print> a = 7;

(a->*print)(std::cout);

在 C++17 中,我会通过采取改进这一点auto*...的指针any_method而不是decltype noise.

公开继承自any有点冒险,就好像有人拿走了any关闭顶部并修改它,tuple of any_method_data将会过时。也许我们应该模仿整个any接口而不是公开继承。

@dyp 在 OP 的评论中写了一个概念证明。这是基于他的工作,并用价值语义进行了清理(从boost::any) 添加。 @cpplearner 基于指针的解决方案用于缩短它(谢谢!),然后我在其之上添加了 vtable 优化。


首先我们使用标签来传递类型:

template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};

该特征类获取存储在any_method:

这将创建一个函数指针类型,以及所述函数指针的工厂,给定一个any_method:

template<class any_method, class Sig=any_sig_from_method<any_method>>
struct any_method_function;

template<class any_method, class R, class...Args>
struct any_method_function<any_method, R(Args...)>
{
  using type = R(*)(boost::any&, any_method const*, Args...);
  template<class T>
  type operator()( tag_t<T> )const{
    return [](boost::any& self, any_method const* method, Args...args) {
      return (*method)( boost::any_cast<T&>(self), decltype(args)(args)... );
    };
  }
};

现在我们不想在每个操作中存储一个函数指针super_any。所以我们将函数指针捆绑到一个 vtable 中:

template<class...any_methods>
using any_method_tuple = std::tuple< typename any_method_function<any_methods>::type... >;

template<class...any_methods, class T>
any_method_tuple<any_methods...> make_vtable( tag_t<T> ) {
  return std::make_tuple(
    any_method_function<any_methods>{}(tag<T>)...
  );
}

template<class...methods>
struct any_methods {
private:
  any_method_tuple<methods...> const* vtable = 0;
  template<class T>
  static any_method_tuple<methods...> const* get_vtable( tag_t<T> ) {
    static const auto table = make_vtable<methods...>(tag<T>);
    return &table;
  }
public:
  any_methods() = default;
  template<class T>
  any_methods( tag_t<T> ): vtable(get_vtable(tag<T>)) {}
  any_methods& operator=(any_methods const&)=default;
  template<class T>
  void change_type( tag_t<T> ={} ) { vtable = get_vtable(tag<T>); }

  template<class any_method>
  auto get_invoker( tag_t<any_method> ={} ) const {
    return std::get<typename any_method_function<any_method>::type>( *vtable );
  }
};

我们可以专门针对 vtable 较小(例如,1 项)的情况,并在这些情况下使用类中存储的直接指针来提高效率。

现在我们开始super_any。我用super_any_t作出声明super_any容易一点。

template<class...methods>
struct super_any_t;

这会搜索超级Any支持的SFINAE方法:

template<class super_any, class method>
struct super_method_applies : std::false_type {};

template<class M0, class...Methods, class method>
struct super_method_applies<super_any_t<M0, Methods...>, method> :
    std::integral_constant<bool, std::is_same<M0, method>{}  || super_method_applies<super_any_t<Methods...>, method>{}>
{};

这是伪方法指针,例如print,我们在全球范围内创建并且constly.

我们将构造它的对象存储在any_method。请注意,如果您使用非 lambda 构造它,事情可能会变得很棘手,因为type这个的any_method用作调度机制的一部分。

template<class Sig, class F>
struct any_method {
  using signature=Sig;

private:
  F f;
public:

  template<class Any,
    // SFINAE testing that one of the Anys's matches this type:
    std::enable_if_t< super_method_applies< std::decay_t<Any>, any_method >{}, int>* =nullptr
  >
  friend auto operator->*( Any&& self, any_method const& m ) {
    // we don't use the value of the any_method, because each any_method has
    // a unique type (!) and we check that one of the auto*'s in the super_any
    // already has a pointer to us.  We then dispatch to the corresponding
    // any_method_data...

    return [&self, invoke = self.get_invoker(tag<any_method>), m](auto&&...args)->decltype(auto)
    {
      return invoke( decltype(self)(self), &m, decltype(args)(args)... );
    };
  }
  any_method( F fin ):f(std::move(fin)) {}

  template<class...Args>
  decltype(auto) operator()(Args&&...args)const {
    return f(std::forward<Args>(args)...);
  }
};

我相信 C++17 中不需要工厂方法:

template<class Sig, class F>
any_method<Sig, std::decay_t<F>>
make_any_method( F&& f ) {
    return {std::forward<F>(f)};
}

这是增强版any。它既是一个any,并且它携带一束类型擦除函数指针,每当包含的any does:

template<class... methods>
struct super_any_t:boost::any, any_methods<methods...> {
private:
  template<class T>
  T* get() { return boost::any_cast<T*>(this); }

public:
  template<class T,
    std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
  >
  super_any_t( T&& t ):
    boost::any( std::forward<T>(t) )
  {
    using dT=std::decay_t<T>;
    this->change_type( tag<dT> );
  }

  super_any_t()=default;
  super_any_t(super_any_t&&)=default;
  super_any_t(super_any_t const&)=default;
  super_any_t& operator=(super_any_t&&)=default;
  super_any_t& operator=(super_any_t const&)=default;

  template<class T,
    std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
  >
  super_any_t& operator=( T&& t ) {
    ((boost::any&)*this) = std::forward<T>(t);
    using dT=std::decay_t<T>;
    this->change_type( tag<dT> );
    return *this;
  }  
};

因为我们存储的是any_methods as const对象,这使得制作super_any更容易一点:

template<class...Ts>
using super_any = super_any_t< std::remove_const_t<std::remove_reference_t<Ts>>... >;

测试代码:

const auto print = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
const auto wprint = make_any_method<void(std::wostream&)>([](auto&& p, std::wostream& os ){ os << p << L"\n"; });

const auto wont_work = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });

struct X {};
int main()
{
  super_any<decltype(print), decltype(wprint)> a = 7;
  super_any<decltype(print), decltype(wprint)> a2 = 7;

  (a->*print)(std::cout);

  (a->*wprint)(std::wcout);

  // (a->*wont_work)(std::cout);

  double d = 4.2;
  a = d;

  (a->*print)(std::cout);
  (a->*wprint)(std::wcout);

  (a2->*print)(std::cout);
  (a2->*wprint)(std::wcout);

  // a = X{}; // generates an error if you try to store a non-printable
}

活生生的例子.

当我尝试存储不可打印的文件时出现错误消息struct X{};在 - 的里面super_any至少在 clang 上似乎是合理的:

main.cpp:150:87: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'X')
const auto x0 = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });

当您尝试分配时会发生这种情况X{}进入super_any<decltype(x0)>.

的结构any_method是足够兼容的pseudo_method它对可以合并的变体的作用类似。


我在这里使用手动 vtable 将类型擦除开销保持在每个指针 1 个super_any。这会为每个 any_method 调用增加重定向成本。我们可以将指针直接存储在super_any非常容易,并且将其作为参数并不难super_any。无论如何,在1个擦除方法的情况下,我们应该直接存储它。


两种不同的any_method相同类型的(例如,都包含函数指针)产生相同类型的super_any。这会导致查找时出现问题。

区分它们有点棘手。如果我们改变super_any采取auto* any_method,我们可以捆绑所有相同类型的any_methods 在 vtable 元组中,然后如果有超过 1 个,则对匹配指针进行线性搜索。编译器应该优化线性搜索,除非您正在做一些疯狂的事情,例如将引用或指针传递给特定的指针。any_method我们正在使用。

然而,这似乎超出了这个答案的范围;目前,这种改进的存在就足够了。


此外,还有一个->*可以在左侧添加一个指针(甚至是引用!),让它检测到这一点并将其传递给 lambda。这可以使其成为真正的“任何方法”,因为它可以使用该方法处理变体、super_anys 和指针。

带着一点if constexpr工作中,lambda 可以在每种情况下分支执行 ADL 或方法调用。

这应该给我们:

(7->*print)(std::cout);

((super_any<&print>)(7)->*print)(std::cout); // C++17 version of above syntax

((std::variant<int, double>{7})->*print)(std::cout);

int* ptr = new int(7);
(ptr->*print)(std::cout);

(std::make_unique<int>(7)->*print)(std::cout);
(std::make_shared<int>(7)->*print)(std::cout);

any_method只是“做正确的事”(即为std::cout <<).

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

类型擦除类型擦除,“有什么”问题吗? 的相关文章

随机推荐

  • 如何测试 IPC::Run3 的退出状态

    我正在尝试测试 Perl 模块 IPC Run3 但难以检查命令是否失败或成功 我知道如果 IPC Run3 的参数有问题 它会发出退出代码 但是如果参数没问题但命令不存在怎么办 我如何测试以下示例 有一个子程序来调用 Run3 sub r
  • 转换为 PHP REST CURL POST

    我们如何将此代码转换为 PHP REST CURL POST POST https apis live net v5 0 me skydrive files access token ACCESS TOKEN Content Type mu
  • 如何在 mac os x 10.7.2 Lion 上安装 PIL

    我尝试过谷歌搜索并查找其他人的问题 但是 我仍然找不到在 mac os x 10 7 2 Lion 上安装 PIL 适用于 python 2 6 或 2 7 的清晰 简单的方法 如果你使用homebrew 您只需安装 PILbrew ins
  • gnuplot 条形图上的 Y 值?

    我可以让 gnuplot 在其条形上显示数据点的精确 y 值或高度 使用 带框 绘制 吗 我希望该图易于阅读 这样就无需将条形顶部与 y 轴对齐并猜测该值是多少 您可以使用标签样式并将其与框样式结合到绘图命令中 标签样式需要 3 列数据 x
  • Oracle 中的 DATEDIFF 函数 [重复]

    这个问题在这里已经有答案了 我需要使用 Oracle 但 DATEDIFF 函数在 Oracle DB 中不起作用 在Oracle中如何编写以下代码 我看到一些使用 INTERVAL 或 TRUNC 的示例 SELECT DATEDIFF
  • 如何在张量流中使用预训练模型作为不可训练子网络?

    我想训练一个包含子网络的网络 我需要在训练期间保持修复 基本思想是在预训练网络 inceptionV3 中添加一些层 new layers gt pre trained and fixed sub net inceptionv3 gt ne
  • 根据第二个数组过滤 numpy 数组中的行

    我有 2 个 2d numpy 数组 A 和 B 我想删除 A 中出现在 B 中的所有行 我尝试过这样的事情 A np isin A B 但 isin 保留 A 的维度 我需要每行一个布尔值来过滤它 编辑 像这样的东西 A np array
  • Vue 模板或渲染函数尚未定义,我两者都没有使用?

    这是我的主要 JavaScript 文件 import Vue from vue new Vue el app 我的 HTML 文件 div div 使用运行时构建的 Vue js 的 Webpack 配置 alias vue vue di
  • 通过计时器在 JDialog 中设置动态 JLabel 文本

    我正在尝试制作一个 JDialog 它将在 JLabel 上向用户显示动态消息 该消息应该是从 1 到 10 的计数 并且应该每秒更改一个数字 问题是 当我调试它时 它在 dia setVisible true 之后立即停止 除非我关闭 J
  • 在 R 中提取日期

    我在 R 中处理日期方面遇到了很大的困难 而在 SPSS 中可以很轻松地做到这一点 但我很乐意留在 R 中完成我的项目 我的数据框中有一个日期列 想要完全删除年份以保留月份和日期 这是我的原始数据的峰值 gt head ds date 1
  • 在不改变宽度的情况下减少条之间的间距

    我正在创建一个像这样的条形图 gender M F numbers males females bars plt bar gender numbers width 0 1 bottom None align center data None
  • 合并具有公共元素和多个数据点的数组

    我正在尝试使用直接的 Javascript 将两个 Javascript 数组合并为一个数组 我正在努力准确地完成以下两个问题中所提出的问题 然而 我的数据有几个点需要合并 而不是单个项目 并且数组之间有一个完全相同的公共元素 以下是其他问
  • 匹配 Swift 中对象的数据类型

    Swift 中如何匹配对象的数据类型 Like var xyz Any xyz 1 switch xyz case let x where xyz as AnyObject println x is AnyObject Type case
  • 如何使用循环来抓取 R 中多个网页的网站数据?

    我想应用一个循环来从 R 中的多个网页中抓取数据 我能够抓取一个网页的数据 但是当我尝试对多个页面使用循环时 我收到了一个令人沮丧的错误 我花了几个小时修修补补 但无济于事 任何帮助将不胜感激 这有效 GET COUNTRY DATA li
  • 数据帧 R 中的成对减法

    我有一个包含 576 行和 5 列的数据框 如下所示 Sample Value1 Value2 A 23 2 NA A 21 5 23 5 A 22 4 22 56 B 20 56 26 54 B 21 5 25 3 B 22 3 24 6
  • 替换 snprintf(3) 的 C++ 习惯用法是什么?

    我有一些 C 代码 在解析某个文件头失败时需要生成错误消息 在这种情况下 我需要确保标头中的某个 4 字节字段是 OggS 如果不是 则返回一条错误消息 例如 invalid capture pattern FooB waiting Ogg
  • jQuery: text() 和 html() 之间有什么区别?

    jQuery 中的 text 和 html 函数有什么区别 div html a href example html Link a b hello b vs div text a href example html Link a b hel
  • 转换unicode

    我有一个UITextField输入一个unicode值 当我点击UIButton需要将其转换并显示在UILabel 下面的代码对我来说工作正常 我的代码中的unicode NSString str NSString stringWithUT
  • 范围解析运算符和常量

    我们来看下面的代码 include
  • 类型擦除类型擦除,“有什么”问题吗?

    所以 假设我想使用类型擦除来键入擦除 我可以为变体创建伪方法 以实现自然的 pseudo method print auto self auto os os lt lt self std variant