如何编写更具可读性的c++20概念?

How to write more readable c++20 concepts?

我正在编写一个模板 class,将用户提供的 class 包装在一个精心制作的包装器中。我正在寻找一种更好的方法来使用 C++20 概念强制执行目标类型的接口要求。

(准确的应用程序是一个模板 class,它简化了将共同等待调用编组到单独的非协程执行上下文并再次返回到执行的协程线程。想想异步 i/o 包装器,如果必须的话。考虑执行线程安全的异步超时、完成回调、拆解以及在不泄漏的情况下分派回执行的原始协程线程所需的非平凡状态机。)

template <typename T>
concept IoCoServiceImplementation = requires(
    T t,
    typename T::return_type x,
    CoServiceCallback<typename T::return_type> *callback)
{
    {t.Execute(callback)} -> std::same_as<void>;
    {t.Cancel(callback)} -> std::same_as<bool>;
};

template <IoCoServiceImplementation SERVICE_IMPLEMENTATION>
class CoServiceAwaitable {
      ...
 };

我要的简洁:IoCoServiceImplementation必须提供两个方法:

class AnImplementation {
public:
       using return_type = RETURN_TYPE;
       void Execute(CoServiceCallback<return_type>*);
       bool Cancel(CoServiceCallback<return_type>*);
 }

请注意概念和示例在可读性方面的显着差异。还值得注意的是,生成的错误消息不提供所需的实际类型。您必须浏览概念声明的源代码才能弄清楚当您获得 error blahblah requires {t.Execute(callback)}.

时回调的含义

虽然我可以想出十几种方法来破解需要我想要的方法的概念,但其中 none 似乎具有足够的可读性和自我记录。而且我破解得越多,声明的可读性就越差,错误消息得到。

我正在考虑探索 std::function<> 是否可以被黑客攻击以生成可读和自我记录的声明。类似于:

 { std::function< void T::Execute(CoServiceCallback<return_type>*)  > };
 { std::function< bool T::Cancel(CoServiceCallback<return_type>*)  > };

(显然这实际上不起作用)。

有更好的方法吗?有没有人提供过概念风格指南?

--

主动,因为我相信这会出现...

你可以争论它应该是 std::same_as<bool> 还是 std::convertable_to<bool>。这是我的想法。我期待一个布尔值。如果那个方法 returns 除了 bool 之外的任何东西,我无法想象它会是什么。所以坚持认为这不是我不期望的事情是合理的。

为当前 C++ 概念实现的缺陷辩护的一个常见论点:概念应该强制执行您可以 做的事情,而不是精确的实现。在这种情况下,我真的不在乎你做什么;我关心的是让你知道你在做什么

可以使用两个requires子句,先判断T是否有成员类型return_type,可以用来尽早停止概念检查

template<typename T>
concept IoCoServiceImplementation = 
  requires { typename T::return_type; } && 
  requires (
    T& t, CoServiceCallback<typename T::return_type>* callback) {
    { t.Execute(callback) } -> std::same_as<void>;
    { t.Cancel(callback)  } -> std::same_as<bool>;
  };

I'm thinking about exploring whether std::function<> can be hacked to produce a readable and self-documenting declarations.

这不是一个好主意,首先,您需要包含额外的(和不必要的)headers,并且它不像 return-type-requirement 的复合要求那样直观.

对我来说听起来像 https://quuxplusone.github.io/blog/2019/09/18/cppcon-whiteboard-puzzle/。正确的惯用答案是 不要那样做。 您现有的概念完全可读,您可以通过删除评论中每个人的 -> std::same_as<...> 后缀使其更具可读性反对。

使模板更具可读性的 long-standing 技巧是使用默认模板参数作为“穷人的类型别名”,如下所示:

template<class T, class R = typename T::return_type>
concept IoCoServiceImplementation =
  requires (T t, R x, CoServiceCallback<R> *cb) {
    t.Execute(cb);
    t.Cancel(cb) ? true : false;
  };

template <IoCoServiceImplementation SERVICE_IMPLEMENTATION>
class CoServiceAwaitable {
  ...
};

请注意,我们不关心 t.Execute(cb) returns 是什么,因为我们不会使用它。我们不关心 精确地 是什么类型 t.Cancel(cb) returns,只要我们可以像布尔值一样在它上面分支​​。

另请注意,特定的 typename 关键字在 C++20 中是可选的;如果你想写 class R = T::return_type,并且你的编译器接受它,请放心。

实际上,您的参数 R x 目前未使用。也许我应该删除它,但我假设你会在其他一些 simple-requirement 中使用它,你只是为了简洁而忽略了它。