简体   繁体   English

什么是C ++运行时概念?

[英]What Are C++ Run-Time Concepts?

I've been looking around on the web for details on C++ concepts lately and have found several references to what several papers call ' run-time concepts .' 我最近一直在网上浏览有关C ++概念的详细信息,并且已经找到了几篇论文所谓的“ 运行时概念” How exactly do they differ from compile-time concepts, why were they introduced in the first place, how will they be implemented, and why are they important for the future of C++? 它们与编译时概念有何不同,为什么它们首先被引入,它们将如何实现,以及它们为什么对C ++的未来很重要? From glancing at the papers, I get the general idea that run-time concepts are intended to alleviate the current tension that currently exists between object-oriented and generic code, but I don't get much else out of them. 通过浏览论文,我得到了一个大概的观点,即运行时概念旨在缓解当前存在于面向对象和通用代码之间的紧张局面,但我没有从中得到更多其他东西。

This is my understanding of what is going on. 这是我对正在发生的事情的理解。 It starts from a different angle: type erasure. 它从不同的角度开始:类型擦除。

std::function<void()> is an example of a type-erasure class. std::function<void()>是类型擦除类的一个例子。 It takes the concepts of "invocation with no arguments, and returning nothing", together with the helper concepts of "copy construct" and "destroy", and wraps it into a neat little package. 它采用“无参数调用,不返回任何内容”的概念,以及“复制构造”和“销毁”的辅助概念,并将其包装成一个整洁的小包。

So you can do 所以你可以做到

void groot () { std::cout << "I am groot!\n"; }
std::function<void()> f = groot;
f();

and groot is invoked. groot被调用。 Or we can pass a lambda, or a function object, or a std::bind expression, or a boost::function to a std::function and invoke it. 或者我们可以将一个lambda,或一个函数对象,或一个std::bind表达式,或一个boost::function传递给std::function并调用它。

All of those types can be copied, destroyed and invoked: so std::function can consume them and produce a single run-time interface. 所有这些类型都可以被复制,销毁和调用:因此std::function可以使用它们并生成单个运行时接口。 Other than the operations that they support, the types that std::function can store and execute are unrelated. 除了它们支持的操作之外, std::function可以存储和执行的类型是不相关的。 There is no class hierarchy that relates the function groot to a lambda, or to boost::function . 没有类层次结构将函数groot与lambda或boost::function

The constructor of std::function<void()> that takes things that aren't std::function s type-erases its argument under the concepts of copy, destroy and invoke with signature void() . std::function<void()>的构造std::function<void()>采用非std::function s类型的东西 - 在复制,销毁和调用的概念下使用签名void()删除其参数。

We start with this: 我们从这开始:

template<class Sig>
struct func_type_eraser;

template<class R, class... Args>
struct func_type_eraser<R(Args...)> {
  // invoke:
  virtual R operator()(Args...) const = 0;
  // copy:
  virtual func_type_eraser* clone() const = 0;
  // destroy:
  virtual ~func_type_eraser() {};
};
template<class Sig, class T>
struct func_type_eraser_impl; // TODO!

Here we have the 3 concepts of copy, destroy and invoke, each represented as a pure-virtual function. 这里我们有3个复制,销毁和调用的概念,每个概念都表示为纯虚函数。

template<class Sig>
struct function;
template<class R, class... Args>
struct function<R(Args...)> {
  std::unique_ptr<func_type_eraser<R(Args...)>> pImpl;
  // invoke:
  R operator()( Args... args ) const {
    return (*pImpl)( std::forward<Args>(args)... );
  }
  // destroy:
  ~function() = default;
  // copy:
  function(function const& o) : pImpl( o.pImpl ? o.pImpl->clone() : nullptr ) {}
  // move:
  function(function&&) = default;
  // TODO: operator=

  // technical issues, ignore:
  function(function& o) : function(const_cast<function const&>(o)) {}
  function(function const&& o) : function(o) {}

  // type erase:
  template<class T>
  function(T&& t) : pImpl( new func_type_eraser_impl<R(Args...), std::decay_t<T>>{std::forward<T>(t)} )
  {}
};

Here, we wrap the concepts up we want to support into what is known as a Regular type -- a value-type type. 在这里,我们将我们想要支持的概念包装成所谓的Regular类型 - 值类型类型。 We have an underlying pointer and virtual hierarchy (a small one, as yet unseen), but the type function looks just like an int -- you can copy, assign, etc it. 我们有一个底层指针和虚拟层次结构(一个小的,尚未看到),但类型function看起来就像一个int - 你可以复制,分配等。

Each of the concepts -- invoke, copy, move, destroy -- is forwarded to the pImpl (except move , which we can implement efficiently at this layer). 每个概念 - 调用,复制,移动,销毁 - 都被转发到pImplmove除外,我们可以在这一层有效地实现)。

Only half of the type erasure work is done here. 只有一半类型的擦除工作在这里完成。 This part lets us assign anything to our function class instances. 这部分让我们为function类实例赋值。 We can do a bit better by testing that T passes the concept requirements -- that it can be copied, destroyed, and invoked with the required signature -- before admitting it to our constructor. 我们可以通过测试T传递概念要求 - 它可以被复制,销毁并使用所需的签名调用 - 然后将其传递给我们的构造函数来做得更好。 (The current C++ std::function fails to do this, to much annoyance). (当前的C ++ std::function无法做到这一点,令人烦恼)。

The last part of the type erasure is…: 类型擦除的最后一部分是......:

template<class R, class... Args, class T>
struct func_type_eraser_impl<R(Args...), T> : func_type_eraser<R(Args...)> {
  // type erase storage:
  T t;
  // invoke:
  virtual R operator()(Args... args) const override {
    return t( std::forward<Args>(args)... );
  }
  // copy:
  virtual func_type_eraser_impl* clone() const override {
    return new func_type_eraser_impl{t};
  }
  // destroy:
  virtual ~func_type_eraser_impl() {}
};

…where we implement the concept interfaces exposed in func_type_eraser for a particular type T . ...我们在func_type_eraser为特定类型T实现概念接口。

Now we have 4 concepts, 3 of which are type erased, and one handled by our regular type wrapper, and we can store anything that supports those 3 concepts. 现在我们有4个概念,其中3个是类型擦除的,一个是由我们的常规类型包装器处理的,我们可以存储支持这3个概念的任何东西。

We can go a step further: 我们可以更进一步:

We can even support anything that the client can supply functions to support those concepts. 我们甚至可以支持客户端可以提供支持这些概念的任何功能。

The easiest way of doing this is to invoke a free function, like std::begin , in a context that allows for ADL (argument dependent lookup). 最简单的方法是在允许ADL(依赖于参数的查找)的上下文中调用自由函数,如std::begin

Have our type erasure implementation that instead of directly interacting with the object, instead invokes the free function in the ADL context. 让我们的类型擦除实现,而不是直接与对象交互,而是调用ADL上下文中的自由函数。

Provide a default implementation of that function that does anything from "fails" to "checks for a method .begin() and invokes it" or "does an inefficient version" or "examines the properties of the type passed, and determines a reasonable way to do the task". 提供该函数的默认实现,它执行从“失败”到“检查方法.begin()并调用它”或“执行低效版本”或“检查传递类型的属性,并确定合理方式”的任何操作做任务“。

With this technique, we can allow clients to extend our type erasure, and use broader concepts. 通过这种技术,我们可以允许客户端扩展我们的类型擦除,并使用更广泛的概念。

As a concrete example, imagine we had the concept printable. 作为一个具体的例子,假设我们有可打印的概念。 Something is printable if it has ostream << X overloaded, or if it has print(X) overloaded. 如果它有ostream << X重载,或者它有print(X)重载,则可以print(X)某些内容。

We add print_it to our type erasure interface. 我们将print_it添加到我们的类型擦除接口。 It using impl_namespace::print , then does a print(t) . using impl_namespace::print ,然后执行print(t)

impl_namespace::print(X) simply does a cout << X . impl_namespace::print(X)只是做一个cout << X

This is all decoupled. 这一切都是分离的。 You can take a type that someone else wrote with no concept of printing, add the print concept via a free function in its namespace, and then pass it to our type erasure system and the type erasure system hooks it up. 您可以采用其他人编写的类型,没有打印概念,通过其命名空间中的自由函数添加打印概念,然后将其传递给我们的类型擦除系统,类型擦除系统将其连接起来。

See this channel 9 video for an example of someone using similar techniques to build a toy document with infinite undo and display that can be extended to an arbitrary number of types, including built-in types. 请参阅此频道9视频,了解使用类似技术构建具有无限撤消和显示的玩具文档的人的示例,该文档可以扩展为任意数量的类型,包括内置类型。

Now, imagine language support for this. 现在,想象一下语言支持。 Being able to describe a set of concepts you want to type erase, and say "build a Regular type that erases these types". 能够描述您想要键入擦除的一组概念,并说“构建一个擦除这些类型的常规类型”。

If you have an algorithm that is supported by said other concepts, you could then say "type erase support for this algorithm". 如果您拥有所述其他概念支持的算法,则可以说“对此算法进行类型擦除支持”。 Any clients that are aware of the algorithm type erasure and they have better support for it can automatically have a custom-created one added to your interface. 任何了解算法类型擦除的客户端都可以自动为您的界面添加自定义创建的客户端。 Those that don't can use the type erased concepts you provided to implement it. 那些不能使用您提供的类型擦除概念来实现它。

At the point of type erasure, where your concepts are being taken from understood at compile time to virtual and run time, the type-erasure support for your algorithm can be very efficient, even if the support for concepts on your type was concept-map based (ie, custom functions where provided to solve the problems. Your type is not naively copyable, but there is a clone function that copies it to suitable storage, say). 在类型擦除时,您的概念在编译时被理解为虚拟和运行时,对您的算法的类型擦除支持可以非常高效,即使对您的类型的概念的支持是概念图基于(即,提供解决问题的自定义函数。您的类型不是天真可复制的,但有一个克隆函数将其复制到合适的存储,比如说)。 The algorithm concept type-erasure can take into account the full compile time concept mapping instead of the runtime virtual concept mapping, giving performance gains even if there isn't a fundamentally faster algorithm. 算法概念类型擦除可以考虑完整的编译时概念映射而不是运行时虚拟概念映射,即使没有基本上更快的算法也能提高性能。

If done with extreme care, you can take a type erasure object with fewer concepts and extend it to one with more concepts if the new concepts are supported by the fewer concepts. 如果非常谨慎地完成,您可以使用较少概念的类型擦除对象,并在较少概念支持新概念的情况下将其扩展为具有更多概念的对象。 Clients who "did not know" you wanted a fast binary search (say) would end up supporting it from their run-time interface: those that did would provide you with a fast binary search customized for your type. “不知道”你想要快速二进制搜索(例如)的客户最终会从他们的运行时界面支持它:那些能够为你提供为你的类型定制的快速二进制搜索。

Taking another step, you could have optional concept support in your type erasure class. 再迈一步,您可以在类型擦除类中使用可选的概念支持。 As an example, a type erased iterator might optionally support random access iteration. 作为示例,类型擦除迭代器可以可选地支持随机访问迭代。 Algorithms that accept iterators might test for random access iteration, and if so create a better implementation. 接受迭代器的算法可能会测试随机访问迭代,如果是,则创建更好的实现。 The concept of binary searching on a range might check if the range has binary search concept support, and if not if it has random access support, and failing that use foward iterator version of binary search (O(n) advances, O(lg(n)) comparisons). 在范围上进行二进制搜索的概念可能会检查范围是否具有二进制搜索概念支持,如果没有,则检查是否具有随机访问支持,并且如果没有使用,则使用二进制搜索的迭代器版本(O(n)advance,O(lg( n))比较)。 In each case it could use the "more specialized" implementation. 在每种情况下,它都可以使用“更专业”的实现。

All of this parallels how concepts work at compile time. 所有这些都与概念在编译时的工作方式相似。 Except, it occurs at run time, and has that extra type erasure system. 除此之外,它发生在运行时,并具有额外的类型擦除系统。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM