按值传递和 std::move 与转发引用

Pass-by-value and std::move vs forwarding reference

我经常遇到按值传递和移动成语:

struct Test
{
    Test(std::string str_) : str{std::move(str_)} {}
    std::string str;
};

但在我看来,在某些情况下,传递 const 引用或右值引用都可以保存一个副本。类似于:

struct Test1
{
    Test1(std::string&& str_) : str{std::move(str_)} {}
    Test1(std::string const& str_) : str{str_} {}
    std::string str;
};

或者可能使用转发引用来避免同时编写两个构造函数。类似于:

struct Test2
{
    template<typename T> Test2(T&& str_) : str{std::forward<T>(str_)} {}
    std::string str;
};

是这样吗?如果是这样,为什么不使用它呢?

此外,C++20 似乎允许使用自动参数来简化语法。我不确定这种情况下的语法是什么。考虑:

struct Test3
{
    Test3(auto&& str_) : str{std::forward<decltype(str_)>(str_)} {}
    std::string str;
};

struct Test4
{
    Test4(auto str_) : str{std::forward<decltype(str_)>(str_)} {}
    std::string str;
};

编辑:

建议的问题提供了信息,但没有提及“汽车”案例。

But it seems to me that passing by either const reference or rvalue reference can save a copy in some situations.

确实如此,但它需要更多的重载(甚至最糟糕的是有几个参数)。

按值传递和移动成语有(最坏的情况)多走一步。大多数时候这是一个很好的权衡。

maybe using a forwarding reference to avoid writing both constructors.

转发引用有其自身的缺陷:

  • 不允许参数使用 {..} 语法,因为 {..} 没有类型。
    Test2 a({5u, '*'}); // "*****"
    
    不可能的。
  • 不限于有效类型(需要额外的 requires 或 SFINAE)。
    Test2 b(4.2f); // Invalid, but `std::is_constructible_v<Test2, float>` is (falsely) true.
    
    会在构造函数内部产生错误,而不是在调用点(因此错误消息不太清楚,SFINAE 不可能)。
  • 对于构造函数,它可以优先于复制构造函数(对于非常量左值)
    Test2 c(a); // Call Test2(T&&) with T=Test2&
                // instead of copy constructor Test2(const Test2&)
    
    会产生错误,因为 std::string 不能从 Test2&.
  • 构造

添加到 by Jarod42 和建议的 dupes(1),您可以通过限制模板的有效类型来克服前向引用方法的缺陷参数包.

#include <string>
#include <concepts>

struct Test
{
    template<class... Args>
        requires std::constructible_from<std::string, Args...>
    Test(Args&&... str_)
        : str( std::forward<Args>(str_)... )
    {}
    std::string str;
};

int main()
{
    Test a{"So far, so good..."};

    Test b{5u, '*'};      // -> "*****"

//    Test b({5u, '*'});  // It works too.
    
    Test c{b};

//    Test d(4.2f);
// error: no matching constructor for initialization of 'Test'
}

(1) Is the pass-by-value-and-then-move construct a bad idiom?