In this answer they suggest to use the following code:
#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";
}
The author linked to a demo that compiles with Clang++/G++ . However, this code does not compile for me in MSVC++2017 .
The error message is:
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>
]
So what is the difference between return { std::forward<F>(f) };
and return Finally<F>{ std::forward<F>(f) };
that one compiles, but the other doesn't?
So what is the difference between
return { std::forward<F>(f) };
andreturn Finally<F>{ std::forward<F>(f) };
The former initializes the return object in place. So when you have:
X foo() { return {a, b, c}; }
auto&& res = foo();
This will construct an X
in place and then bind res
to it. There is no moving anywhere at all. Not a move that would be elided thanks to RVO, not a move that would be elided thanks to guaranteed elision in C++17 with the new value categories. At no point are we even considering moving anything. There is only ever one X
(even in C++11, even with -fno-elide-constructors
, because there is no constructor call to elide).
By contrast, this:
X bar() { return X{a, b, c}; }
auto&& res2 = bar();
Before C++17, would create a temporary X
and then move it into the return of bar()
. This would be a candidate for RVO and the move would surely be elided away, but in order to elide a move, a move has to actually be possible to begin with. And in our case, X
's move constructor is deleted so this code is ill-formed. This leads to a situation that is best described by Richard Smith's comment in P0135R0:
// error, can't perform the move you didn't want, // even though compiler would not actually call it
After C++17, there is no temporary. We have a prvalue of type X
that we are using to initialize the return object of bar()
, so we end up simply initializing the return object from the prvalue's initializer. The behavior is equivalent to the foo()
example above. No move will be done.