0

Here is the code snippet:

#include <iostream>
#include <vector>

template <typename T>
class Point
{
    public:
        Point() = default;  
        Point(T x, T y):x_(x),y_(y){}
        //Point(const Point&) = delete;
        Point(Point&&) = default;
        T x_;
        T y_;
};

#define INIT_VECTOR

int main ()
{
#ifdef INIT_VECTOR
  std::vector<Point<int>> myvector={{1,1},{2,2}};   //why it does not compile?
#else
  std::vector<Point<int>> myvector;                //whereas, this works well.
  myvector.reserve(5);
#endif

  myvector.emplace_back();
  auto& point = myvector.back();
  point.x_ = 6;
  point.y_ = 9;

  std::cout << "myvector contains:" << std::endl;
  for (auto& pt: myvector)
  {
        std::cout << ' ' << pt.x_ << "," << pt.y_ << std::endl;
  }
  return 0;
}

It's clear that Point<T> has no copy constructor, whereas it has move constructor. My question is why std::vector<Point<int>> myvector={{1,1},{2,2}}; does not compile? Could somebody shed some light on this matter? I thought and thought, but I still can't figure out why.

John
  • 2,963
  • 11
  • 33
  • Performance note: The non-default constructor should be `Point(T x, T y):x_(std::move(x)),y_(std::move(y)){}`; costs you nothing when `T` is a primitive type like `int`, but if you're using a more expensive type like, say, `mpz_class`, it replaces an unnecessary copy with a cheap transfer of ownership. – ShadowRanger May 02 '22 at 02:41
  • 1
    Pretty sure the answer to this is found in [initializer_list and move semantics](https://stackoverflow.com/q/8193102/364696); it's not a strict duplicate, but it's basically the same issue (someone trying to write the same code `vector` would need to do the same work, and it doesn't work). Despite what you'd hope, `initializer_list`s iterate by `const` iterators, so it *can't* move from them into the `vector`, it has to perform copies. You disabled copying, so it can't do it. – ShadowRanger May 02 '22 at 02:44
  • @ShadowRanger Thank you for the guidance. A question raises, if it is written as `x_(std::move(x)), y_(std::move(y)`, then `T` is should have **either** a move constructor **or** a copy constructor, whereas if it is written as `x_(x), y_(y)`, then `T` **must** have a copy constructor. Am I right? – John May 02 '22 at 02:48
  • Yep. The actual `x` or `y` passed to the constructor might have either of those applied, or avoid copy/move construction entirely when the object is constructed in the call itself by the caller, depending on how the caller invokes it (so if the object has no copy constructor, the caller would have to `std::move` something or pass it a newly constructed value that it elides copy/move construction entirely). But within the function, you have control, and moving means you'll support it if it supports either copy or move construction. – ShadowRanger May 02 '22 at 02:53
  • (All that said, my C++ expertise is not sufficient to say whether a compiler might only require the *ability* to copy or move construct, then *actually* elide all the temporaries entirely; I'd kind of hope they would, but I'm regularly disappointed on these things) – ShadowRanger May 02 '22 at 02:54
  • @ShadowRanger Elision is not possible here. The parameters of the `Point` constructor must be constructed and the members must also always be constructed. Of course a compiler can still optimize under the as-if rule, especially when inlining. If the temporary elision is really important, then the class shouldn't declare any constructors and use aggregate-initialization instead. That can skip the constructor parameter construction. Or the constructor should take arguments by-reference, in which case it can at least in some cases skip the intermediate object (depending on how it is called). – user17732522 May 02 '22 at 03:03
  • @ShadowRanger For the said post(i.e.:[initializer_list and move semantics](https://stackoverflow.com/questions/8193102/initializer-list-and-move-semantics)), I still have a question.Though `begin()` and `end()` for `initializer_list` return `const T *`. Since the code is written as `auto it = list.begin()`, `it` is not `const` indeed, so I think `bar(std::move(*it));` should be compiled. Where am I wrong? I am really confused now. Thanks a lot. – John May 02 '22 at 03:06
  • `auto it = list.begin();` where `list.begin()` returns `const T*` means `it` *itself* is not `const` (because it's a mutable *pointer*; you can change where it points to just fine with `++it` or the like), but it still *points* to a `const T`. The `auto` without `const` only means the pointer itself is definitely non-`const`, but `*it` *remains* `const`. – ShadowRanger May 02 '22 at 03:23

0 Answers0