专业的编程技术博客社区

网站首页 > 博客文章 正文

C++ 20 Concept是怎样简化您的代码的

baijin 2024-10-12 02:08:58 博客文章 15 ℃ 0 评论

今天我想谈谈C++ 20的Concept以及它们如何帮助您简化代码。另外,也会让代码更准确。

设想一个类模板,我们希望在其中禁用某个方法。

template<typename T, bool enable = true>
class Sample
{
  public:
  int DisableThisMethodOnRequest() { return 42; }
};

通常的方法是SFINAE,使用enable_if。

class Sample
{
  public:
  std::enable_if_t<enable, int> DisableThisMethodOnRequest() { return 42; }
};

好吧,enable_if并不完全满足我的要求,但它看起来简洁易读。遗憾的是,这段代码并没有达到我们想要的效果。SFINAE在这里不适用,因为我们将它应用于方法,而不是类。要使其生效,我们必须使DisableThisMethodOnRequest本身具有模板:

class Sample
{
  public:
  template<typename Dummy = void>
  std::enable_if_t<enable, int> DisableThisMethodOnRequest()
  {
    return 42;
  }
};

为了避免用户必需提供此模板参数,我们将其设置为void作为默认值,并给它一个古怪的名字。希望这样可以对所有用户暗示不要使用这个模板参数。好吧,对初学者来说这可能不太清晰。另外,我们把这个方法作为一个模板,可能引发其他问题。现在,C++ 20很快就会出现在我们的手中。目前,ISO正在评估最终文档。设计已经关闭,所以我们可以安全地假定Concept将在C++ 20中,并且在最新的工作草案中指定。

使用C++20 Concept

Concept在手,我们可以重写前面的示例:

template<typename T, bool enable = true>
class Sample
{
  public:
  int DisableThisMethodOnRequest() requires(enable) { return 42; }
};

我们可以去掉enable_if,方法不再需要声明为模板。通过简单地在函数声明后面加上一个requires子句,我们可以得到相同的结果。

除开这次简短清晰之外,我个人并不喜欢这种dummy默认模版参数。当使用可以提供类型或方法信息的IDE时,它们对用户是可见的,但实际上它们不是用户应该看到或者关心的。

requires的三个合法位置

requires子句

template<typename T>
requires YourRequirementOrConcept<T>
void func();

后置requires子句

void func() requires YourRequirementOrConcept<T>;

这是我们在示例中使用的版本。这里您可以看到它还可以与模板和模板参数组合使用。

作为限制性模版参数

template<YourRequirementOrConcept T>
void func();

在这里,requires子句嵌入到您的YourRequirementOrConcept中。如果您有一个多次使用的requirement,这可能是最常见的形式。它使您能够为它提供一个合适的名称,以及一个唯一的实现。这里的Concept可以是这样的:


template<typename T>
concept YourRequirementOrConcept = requires SomeThing;

还有更多

有时我们只想提供一个默认的构造函数,只依赖于某些条件。这个例子是一个包装器类型,它应该模拟包装器类型的行为。这是一个启用“如果”的作业。下面是一个例子:

#include <type_traits>

template<bool WithDefaultCtor>
class ClassWithOptionalDefaultCtor
{
  public:
  template<typename std::enable_if<WithDefaultCtor, int>::type = 0>
  ClassWithOptionalDefaultCtor()  // 1  we cannot say =default here
  {
  }
};

int main()
{
  ClassWithOptionalDefaultCtor<true> t{};
}

注意,在//1处我们不能使用=default,因为这不是默认构造函数。它是一个看起来像默认构造函数的方法模板。这样的事情不能默认,因为编译器不知道它是什么。如果不将变量添加到构造函数初始值设定项列表中,上面的非常糟糕的解决方案会导致未初始化的变量。假设我们讨论的是包装器,它可能只有一个成员值。添加此成员并不麻烦。只是这个解决方案似乎不是很通用。

C++ 20可以让我们将代码改写为:

template<bool WithDefaultCtor>
class ClassWithOptionalDefaultCtor
{
  public:
  ClassWithOptionalDefaultCtor() requires(WithDefaultCtor) =
      default;  // 1  way better
};

int main()
{
  ClassWithOptionalDefaultCtor<true> t{};
}

漂亮的代码!首先请注意,type_traits include连同丑陋的enable_if一起消失了。仅此一项就带来了编译时的加速,是否显著则是另一个问题。如果requires条件变得更复杂并且需要其他概念,编译加速也可能反转。

与前面的示例一样,我认为requires子句使代码更具可读性。但最棒的是我们现在可以使用=default!有了C++ 20的Concept,我们可以在这里拥有一个真正的默认构造函数。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表