Search code examples
c++c++14visual-studio-2017c++17auto

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


Solution

  • 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. 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.