支持 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) };
一个编译一个不编译有什么区别呢?
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()
示例。不会采取任何行动。
在
#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) };
一个编译一个不编译有什么区别呢?
So what is the difference between
return { std::forward<F>(f) };
andreturn 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()
示例。不会采取任何行动。