支持 MSVC++2017 的“auto&&”

Support for `auto&&` for MSVC++2017

他们建议使用以下代码:

#include <iostream>

template <typename F>
class Finally {
    F f;
public:
    template <typename Func>
    Finally(Func&& func) : f(std::forward<Func>(func)) {}
    ~Finally() { f(); }

    Finally(const Finally&) = delete;
    Finally(Finally&&) = delete;
    Finally& operator =(const Finally&) = delete;
    Finally& operator =(Finally&&) = delete;
};

template <typename F>
Finally<F> make_finally(F&& f)
{
    return Finally<F>{ std::forward<F>(f) }; // This doesn't compile
    //This compiles: return { std::forward<F>(f) };
}


int main()
{
    auto&& doFinally = make_finally([&] { std::cout<<", world!\n"; });
    std::cout << "Hello";
}

作者链接到一个使用 Clang++/G++ 编译的演示。但是,此代码无法在 MSVC++2017 中为我编译。

错误信息是:

source_file.cpp(20): error C2280: 'Finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>>::Finally(Finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>> &&)': attempting to reference a deleted function
source_file.cpp(12): note: see declaration of 'Finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>>::Finally'
source_file.cpp(26): note: see reference to function template instantiation 'Finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>> make_finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>>(F &&)' being compiled
        with
        [
            F=main::<lambda_9000fb389e10855198e7a01ce16ffa3d>
        ]

那么return { std::forward<F>(f) };return Finally<F>{ std::forward<F>(f) };一个编译一个不编译有什么区别呢?

Demo

So what is the difference between return { std::forward<F>(f) }; and return Finally<F>{ std::forward<F>(f) };

前者就地初始化return对象。所以当你有:

X foo() { return {a, b, c}; }
auto&& res = foo();

这将在适当的位置构造一个 X,然后将 res 绑定到它。根本没有任何动静。不会因为 RVO 而被忽略的移动,也不会因为 C++17 中具有新值类别的保证省略而被忽略的移动。我们甚至都没有考虑移动任何东西。只有一个 X(即使在 C++11 中,即使使用 -fno-elide-constructors,因为没有构造函数调用来省略)。

相比之下,这个:

X bar() { return X{a, b, c}; }
auto&& res2 = bar();

在 C++17 之前,会创建一个临时的 X,然后将其移动到 bar() 的 return 中。这将是 RVO 的候选,并且该移动肯定会被删除,但为了删除移动,移动必须实际上是可能的。在我们的例子中,X 的移动构造函数被删除,因此这段代码格式错误。这导致了 Richard Smith 在 P0135R0:

中的评论最能描述的情况
// error, can't perform the move you didn't want,
// even though compiler would not actually call it

C++17之后,就没有临时了。我们有一个 X 类型的纯右值,我们用它来初始化 bar() 的 return 对象,所以我们最终只是从纯右值的初始化器中初始化 return 对象。该行为等同于上面的 foo() 示例。不会采取任何行动。