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



在《Effective Modern c++ "》Scott Meyers给出的建议(第26/27条)是"避免过度使用通用引用"。他这样做的理由是,在对包含通用引用的重载函数的几乎所有调用中,编译器都会解析到通用引用,即使这通常不是您希望它解析的函数。(所以我认为这段代码很糟糕?)

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

上面的例子是高度人为的,很可能被两个函数所取代。

我认为另一个更难解决的例子是他在项目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);

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 */ {}

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

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

我的问题有多个部分:

  • 甚至有一种方法来禁止使用c++概念的特定类型?(也许与我所做的相似)
  • 有没有更好的方法告诉编译器不要使用转发构造函数?(如果不需要的话,我不想让我复制的变量成为常量,也不想显式定义复制/移动构造函数)
  • 如果有一种方法可以做到我所建议的,这是一个普遍适用的解决方案,Scott Meyers表达的问题?
  • 对类型应用模板约束是否会自动阻止该类型成为通用引用?

我会说没有。我的意思是,概念有帮助,因为语法比以前更好,但问题还是一样的。

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

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

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

any a = 42; // calls any(T&&), with T=int
any b = a;  // calls any(T&&), with T=any

因为any本身当然是可复制构造的。这使得构造函数模板是可行的,并且是一个更好的匹配,因为它是一个不太符合const条件的引用。

因此,为了避免这种情况(因为我们希望b持有int,而不是持有any持有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&);
};

这与我们在c++ 17和更早的时候Scott Meyers写他的书时必须做的事情是一样的。至少,解决问题的机制是一样的——即使语法更好。

是否有一种方法可以禁止使用c++概念的特定类型?(可能与我所做的类似)

是的,您可以通过添加T与您不想要的类型不同的约束来实现这一点。

但是如果你也不想要const, volatile和reference,请记住从T中删除它们。因为当函数可能以左值类型TArg&调用时,为了得到参数类型T&&=TArg& &&=TArg&,T被推断为TArg&。这就是引用坍缩,这就是所谓的通用/转发引用的工作原理。

template<typename T>
requires (!std::same_as<Foo, std::remove_cvref_t<T>>)
Foo(T&& t);
//...
Foo f{5};
auto f_clone(f); // calls Foo(const Foo&) with added constraint
// (calls Foo(T&&) with T=Foo& without added constraint)

对类型应用模板约束是否会自动阻止该类型成为通用引用?

不,或视情况而定。约束是在模板参数推导后检查的,所以转发引用仍然有效,除非你像上面的示例代码那样用特定的约束排除它。

最新更新