使用 c++ 20 中的概念,通用引用的重载现在更加安全

jar*_*son 7 c++ effective-c++ c++-concepts forwarding-reference c++20

Scott Meyers 所著的《Effective Modern C++》一书中给出了“避免通用引用重载”的建议(第 26/27 项)。他的理由是,在几乎所有对包含通用引用的重载函数的调用中,编译器都会解析为通用引用,即使这通常不是您想要解析的函数。(所以我认为这段代码很糟糕?)

template <typename T>
void foo(T&& t) {
  // some sort of perfect forwarding
}
void foo(string&& t) {
  // some sort of move operation
}
Run Code Online (Sandbox Code Playgroud)

上面的例子是精心设计的,可能会被替换为 2 个函数。

我认为更难解决且不那么做作的另一个例子是他在第 26 项中实际给出的例子。

class Foo {
// a decent amount of private data that would take a while to copy
public:
  // perfect forwarding constructor, usually the compiler resolves to this...
  template <typename T>
  explicit Foo(T&& t) : /* forward the information for construction */ {}
  // constructor with some sort of rValue
  explicit Foo(int);
// both the below are created by the compiler he says
  // move constructor
  Foo(Foo&& foo);
  // copy constructor (used whenever the value passed in is const)
  Foo(const Foo& foo);
}
// somewhere else in the code
Foo f{5};
auto f_clone(f);
Run Code Online (Sandbox Code Playgroud)

Scott 解释说,不是调用移动构造函数或复制构造函数,而是调用转发构造函数,auto f_clone(f)因为编译器规则首先解析为转发构造函数。

在书中,他解释了此方法的替代方案以及通用引用重载的其他一些示例。其中大多数对于 C++11/14/17 来说似乎都是很好的解决方案,但我认为有更简单的方法可以使用 C++20 概念来解决这些问题。除了对转发构造函数的某种约束之外,代码与上面的代码相同:

template <typename T>
  requires = !(typename Foo) // not sure what would need to be put here, this is just a guess
explicit Foo(T&& t) : /* forward the information for construction */ {}
Run Code Online (Sandbox Code Playgroud)

我不知道这是否是正确的语法,我对 C++ 概念非常陌生

对我来说,应用于转发函数的 C++ 概念似乎是一个可以应用于每种情况的通用解决方案,但我不确定

我的问题有多个部分:

  • 有没有办法禁止使用 C++ 概念的特定类型?(也许和我所做的类似)
  • 有没有更好的方法告诉编译器不要使用转发构造函数?(如果不需要,我不想使要复制的变量成为常量或显式定义复制/移动构造函数)
  • 如果有一种方法可以实现我的建议,那么这是否是解决斯科特·迈耶斯所表达的问题的普遍适用的解决方案?
  • 对类型应用模板约束是否会自动阻止该类型成为通用引用?

Bar*_*rry 5

我会说不。我的意思是,概念有帮助,因为语法比我们以前的更好,但它仍然是同样的问题。

这是一个现实生活中的示例:std::any可以从任何可复制构造的类型构造。所以你可以从以下开始:

struct any {
    template <class T>
        requires std::copy_constructible<std::decay_t<T>>
    any(T&&);

    any(any const&);
};
Run Code Online (Sandbox Code Playgroud)

问题是,当你做这样的事情时:

any a = 42; // calls any(T&&), with T=int
any b = a;  // calls any(T&&), with T=any
Run Code Online (Sandbox Code Playgroud)

因为any它本身当然是可复制构造的。这使得构造函数模板变得可行,并且具有更好的匹配性,因为它是一个较少 const 限定的引用。

因此,为了避免这种情况(因为我们想要b持有 an int,而不是持有any持有 an 的 an int),我们必须将自己排除在考虑范围之外:

struct any {
    template <class T>
        requires std::copy_constructible<std::decay_t<T>>
              && (!std::same_as<std::decay_t<T>, any>)
    any(T&&);

    any(any const&);
};
Run Code Online (Sandbox Code Playgroud)

这与我们在 C++17 中以及更早版本 Scott Meyers 撰写他的书时必须做的事情相同。至少,解决问题的机制是相同的——即使语法更好。