2

In this test:

#include <string>

struct thing {
    std::string name_;
};

class test {
    thing id_;
public:
    test(thing id) : id_{std::move(id)} {}
};

I would expect struct thing to have an implicit move constructor, so that class test can use std::move() to initialise its data member.

Clang version 3.4.1 gives this error:

error: no viable conversion from 'typename remove_reference<thing&>::type' (aka 'thing') to 'std::string' (aka 'basic_string<char, char_traits<char>, allocator<char> >')

The problem can be solved by adding a move constructor to struct thing, which of course means that a converting constructor and an explicitly defaulted copy constructor also needs to be added.

I do not understand why I cannot move struct thing around implicitly.

Quentin
  • 62,093
  • 7
  • 131
  • 191
Richard Smith
  • 45,711
  • 6
  • 82
  • 81
  • What compiler are you using? http://ideone.com is compiling just fine. `thing` surely has implicit move constructor, but your compiler interpret `id_{std::move(id)}` as aggregate initialization. – Elohim Meth Aug 26 '15 at 10:09
  • 3
    `id_(std::move(id))` (with parentheses instead of curly braces) works. Waiting for a [tag:language-lawyer] to clear up why aggregate initialization overrides move-construction, or whether it is a bug :) – Quentin Aug 26 '15 at 10:09
  • @Quentin I have an aggregate initialization blind spot. I could not see it. Thanks, all working now. – Richard Smith Aug 26 '15 at 11:32
  • confusing as heck to have [another Richard Smith](http://stackoverflow.com/users/1041090/richard-smith) (one of Clang's primary authors) ask Clang related questions :) – TemplateRex Sep 01 '15 at 09:46

1 Answers1

3

You are using a brace initialization - id_{std::move(id)}. In your case, as though struct thing is a POD (plain old data) it means, that C++ compiler tries to initialize the first member - std::string name_ instead of using a default constructor of struct thing object. Read more about aggregates and PODs.

In this case, because of the braces, class test's constructor is equivalent to something like this:

class test {
    thing id_;
public:
    test(thing id) {
        id_.name_ = std::move(id); // Notice that this causes
                                   // the "no viable conversion" error
    }
};

Solution 1: You need to declare explicitly that you want to use a default struct thing's constructor by using parenthesis instead of braces:

#include <string>

struct thing {
    std::string name_;
};

class test {
    thing id_;
public:
    test(thing id) : id_(std::move(id)) {} // Default c-tor will be used
};

Solution 2: You could also declare a user-defined constructor of struct thing to make it non-POD:

#include <string>

struct thing {
    std::string name_;
    thing(thing&&) {} // Used-defined move constructor
};

class test {
    thing id_;
public:
    test(thing id) : id_{std::move(id)} {} // You can use braces again
};
Community
  • 1
  • 1
psliwa
  • 1,094
  • 5
  • 9