如何使用概念在 C++ 20 中提供默认实现?

How to provide default implementations in C++ 20 using concepts?

我正在试验将 C++20 中引入的 concept 用作静态接口的可能性。到目前为止,我做得很好,只是我无法找到一种方法来为概念提供“默认实现”。

例如,我有一个名为ByteBuffer的概念,它是这样表述的:

template <typename T>
concept ByteBuffer = requires (T t) {
  { t.read_byte() } noexcept -> std::convertible_to<uint8_t>;
  { t.has_byte() } noexcept -> std::same_as<bool>;

  /* default implementation for `t.read_until(...)`? */
};

从逻辑上讲,我应该能够为 t.read_until(...)(省略参数)提供默认实现,并允许具体实现覆盖默认实现。有办法吗?我该怎么做?

如果这不行的话,我觉得加上这样的能力也是合理的。

目前,我不得不求助于 CRTP 来提供外观 class,我认为它比我想象的还要多余。

概念是不是基础类。事实上,概念与其任何模板参数都没有显式或隐式关系。

概念只做一件事:验证一组特定的模板参数是否适合在实例化特定模板时使用。那是 全部.

概念包含一系列表达式和术语,这些表达式和术语应该对给定的模板参数有效。这就是确定一组模板参数是否对特定用途有效所需的全部内容,因此这就是所有概念提供的内容。

如果您需要某种默认功能,则必须使用替代的 C++ 机制来实现。最简单的是一个有自己约束的效用函数:

template<typename T>
concept ByteBufferReadUntil = ByteBuffer<T> &&
  requires(T bb) //Add parameters as appropriate
  {
    { t.read_until() } noexcept -> std::same_as<bool>;
  };

template<ByteBuffer T>
  requires ByteBufferReadUntil<T>
bool read_byte_buffer_until(T &bb)
{
  return t.read_until();
}

template<ByteBuffer T>
bool read_byte_buffer_until(T &bb)
{
  //default implementation for `t.read_until(...)`
}

所以每当你想为任意 ByteBufferread_until 时,你调用 read_byte_buffer_until.

概念实际上只是对类型的约束,因此向概念添加“默认实现”是没有意义的。相反,您可以做的是为 read_until 提供默认实现并使用它,除非某些 ByteBuffer 类型提供了自己的实现。

首先,您可以编写一个单独的概念来检查 read_until:

template <typename T>
concept Readable = requires (T t) {
 { t.read_until() } noexcept -> std::same_as<void>;  // takes no arguments, and returns void for demonstration
};

然后您可以为不提供此功能的类型提供默认实现:

template <typename T>
void read_until_impl(T t) {
   std::cout << "default implementation";
}

template <Readable T>
void read_until_impl(T t) {
    t.read_until();  // call provided member function
}

然后一些使用 ByteBuffer 类型的函数可以这样调用函数:

template <ByteBuffer T>
void use(T t) {
    read_until_impl(t);
}

这里是 demo

如前所述,C++20 概念实际上根本没有提供任何机制来帮助解决这个问题。它们只是 约束模板的谓词。 没什么 更多。

我将提供一个更简洁的替代方案:使用 if constexpr:

template <ByteBuffer BB>
void read_until(BB& buffer) {
    if constexpr (requires { buffer.read_until(); }) {
        buffer.read_until();
    } else {
        while (buffer.has_byte()) {
            buffer.read_byte();
        }
    }
}

或者有时我喜欢使用函数组合来使这一点更清楚,使用类似 Boost.Hof:

auto read_until = first_of(
    [](ByteBuffer auto& bb) requires requires { bb.read_until(); } {
        // specialized impl
    },
    [](ByteBuffer auto& bb) {
        // default impl
    });

这也可以是 overload,因为一个比另一个更受限制,我只是喜欢使用 first_of 因为它使它更清晰(而且 plus 可以在 C++ 中工作17 只有一个基于尾随 return 类型的约束)。

概念不基础类。事实上,概念与其任何模板参数都没有显式或隐式关系。

概念只做一件事:验证一组特定的模板参数是否适合在实例化特定模板时使用。就这些了。

概念包含一系列表达式和术语,这些表达式和术语应该对给定的模板参数有效。这就是确定一组模板参数是否对特定用途有效所需的全部内容,因此这就是所有概念提供的内容。

如果您需要某种默认功能,则必须使用替代的 C++ 机制来实现。最简单的是一个有自己约束的效用函数:

template<typename T>
concept ByteBufferReadUntil = ByteBuffer<T> &&
  requires(T bb) //Add parameters as appropriate
  {
    { t.read_until() } noexcept -> std::same_as<bool>;
  };

template<ByteBuffer T>
  requires ByteBufferReadUntil<T>
bool read_byte_buffer_until(T &bb)
{
  return t.read_until();
}

template<ByteBuffer T>
bool read_byte_buffer_until(T &bb)
{
  //default implementation for `t.read_until(...)`
}

所以每当你想为任意 ByteBuffer 做 read_until 时,你调用 read_byte_buffer_until.