mercredi 3 août 2016

Behavior of overloading forwarding / universal reference(s)

I got confused today, while implementing a forwarding constructor. Here's the code snippet:

# include <iostream>
# include <iterator>
# include <vector>


struct Vec
{
  explicit Vec() = default;

  Vec(Vec&&) = default;
  Vec(const Vec&) = default;

  Vec& operator=(Vec&&) = default;
  Vec& operator=(const Vec&) = default;

  template<typename ...Args> Vec(Args &&...args): // Yes, this is dangerous. SFINAE needed.
    _vec(std::forward<Args>(args)...) {}

  const std::vector<int> & get() const { return _vec; }

  std::vector<int> _vec;
};

As suggested by many of the greatest, the line with the comment is dangerous since it contains overloading (variadic templates) forwarding / universal references, which will probably "monopoly" the overload resolution (against default or implicit generated copy / move constructor). Extra SFINAE constraints on variadic templates are needed to make this work properly.

So as expected, the following code does not compile:

Vec vec(5, 10);
Vec vec_copy(vec); // This line does not compile.

The second line with the copy construction resolves to the forwarding constructor: no surprises here.

But since I was experimenting, I wrote the following wrapper class (just for fun):

struct Wrapper
{
  explicit Wrapper() = default;

  Wrapper(const Vec &vec): _data(vec) {};

  Wrapper(Wrapper&&) = default;
  Wrapper(const Wrapper &rhs)
  {
    Vec vec(rhs._data); // I wrote it like this on purpose.
    _data = vec;
  };

  Wrapper& operator=(Wrapper&&) = default;
  Wrapper& operator=(const Wrapper&) = default;

  Vec _data;
};

And now the following code snippet compiles and runs as (un)expectly (with g++-5.3 -std=c++14 -O3 -Wall):

  Vec vec(5, 10);

  // Console output.      
  std::copy(vec.get().begin(), vec.get().end(),
            std::ostream_iterator<int>(std::cout, " "));

  Wrapper wrap(vec);
  Wrapper warp_copy = wrap; // This copy construction does the "right" thing.

  // Verify copy.
  std::cout << '\n';
  std::copy(vec.get().begin(), vec.get().end(),
            std::ostream_iterator<int>(std::cout, " "));  

And it outputs:

$ ./fwd.exe
10 10 10 10 10
10 10 10 10 10

My question is:

  • Why suddenly the copy construction of Vec objects inside Wrapper class get resolved correctly?
  • I wrote the line Vec vec(rhs._data); on purpose. If I'm understanding correctly here, rhs._data returns an lvalue reference to Vec _data, so this line is essentially the same as the previous Vec vec_copy(vec);, which does not compile outside of the Wrapper class, right?
  • Funny thing, if I do this: Vec vec_dup(wrap._data); in the main, outside the Wrapper class, it does not compile. The same error is reported, saying that it got resolved into the forwarding constructor.

Am I missing something here? Is this the expected behavior? It has been really bugging for a while ...

Aucun commentaire:

Enregistrer un commentaire