Serge Rogatch Serge Rogatch -3 years ago 76
C++ Question

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

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?

Demo

Answer Source

So what is the difference between return { std::forward<F>(f) }; and return 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. Just an 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.

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download