Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visual C++ perfect forwarding compile error

Any reason why this does not compile in Visual Studio C++? I'm using Visual Studio 2017 15.7.1. It compiles in clang and g++:

#include <utility>
#include <vector>

struct Foo {
    Foo(int x) {}
    Foo(Foo const& b) {}
};

struct Bar {
    template <class... Args>
    Bar(Args&&... args)
        : foo(std::forward<Args>(args)...) {}

    Foo foo;
};

void test() {
    std::vector<Bar> v;
    v.emplace_back(123);
}

The error is error C2664: 'Foo::Foo(const Foo &)': cannot convert argument 1 from 'Bar' to 'int'

See https://godbolt.org/g/bKb34v

EDIT: I've submitted this issue here: https://developercommunity.visualstudio.com/content/problem/252470/perfect-forwarding-compiler-bug.html

like image 322
martinus Avatar asked May 14 '18 11:05

martinus


1 Answers

This is your bug, not MSVC's.

  1. Foo's copy constructor is not noexcept and it has no move constructor.
  2. Bar's implicitly declared move constructor is not noexcept either, because it needs to call the aforementioned copy constructor for the Foo data member.
  3. emplace_back may reallocate, and since Bar appears copyable, that reallocation will copy the existing elements to preserve the strong exception safety guarantee.
  4. This copying may be done, depending on the implementation, from a const or non-const Bar lvalue.
  5. Your unconstrained constructor template hijacks copying from a non-const Bar lvalue.
  6. Explosions and fireworks follow.

The fix is to constrain the constructor template so that it doesn't hijack. For example:

template <class... Args, 
          class = std::enable_if_t<std::is_constructible_v<Foo, Args...>>>
Bar(Args&&... args)
    : foo(std::forward<Args>(args)...) {}
like image 80
T.C. Avatar answered Nov 17 '22 03:11

T.C.