jeudi 26 novembre 2015

How can i check if a move constructor is being generated implicitly?

I have several classes for which I wish to check whether a default move constructor is being generated. Is there a way to check post-compile, whether these were generated?

I wish to avoid:

  1. tediously checking the conditions for implicit move ctor generation,
  2. explicitly and recursively defaulting the special member functions of all affected classes, their bases, and their members—just to make sure a move constructor is available.

I have already tried the following and they do not work:

  1. use std::move explicitly—this will invoke the copy ctor if no move ctor is available.
  2. use std::is_move_constructible—this will succeed when there is a copy constructor accepting const Type&, which is generated by default (as long as the move constructor is not explicitly deleted, at least).
  3. use nm -C to check the presence of move ctor [see below].

I tried looking at the generated symbols of a trivial class like this:

#include <utility>

struct MyStruct {
    MyStruct(int x) : x(x) {}
    //MyStruct(const MyStruct& rhs) : x(rhs.x) {}
    //MyStruct(MyStruct&& rhs) : x(rhs.x) {}
    int x;
};
int main() {
    MyStruct s1(4);
    MyStruct s2(s1);
    MyStruct s3(std::move(s1));
    return s1.x+s2.x+s3.x; // make sure nothing is optimized away
}

The generated symbols looks like this:

$ CXXFLAGS="-std=gnu++11 -O0" make -B x; ./x; echo $?; nm -C x | grep MyStruct | cut -d' ' -f3,4,5
g++ -std=gnu++11 -O0    x.cc   -o x
12
.pdata$_ZN8MyStructC1Ei
.pdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.text$_ZN8MyStructC1Ei
.text$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.xdata$_ZN8MyStructC1Ei
.xdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
MyStruct::MyStruct(int)
std::remove_reference<MyStruct&>::type&&

The output is the same when I explicitly default the copy and move ctors (no symbols).

With my own copy and move ctors, the output looks like this:

$ vim x.cc; CXXFLAGS="-std=gnu++11 -O0" make -B x; ./x; echo $?; nm -C x | grep MyStruct | cut -d' ' -f3,4,5
g++ -std=gnu++11 -O0    x.cc   -o x
12
.pdata$_ZN8MyStructC1Ei
.pdata$_ZN8MyStructC1EOKS_
.pdata$_ZN8MyStructC1ERKS_
.pdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.text$_ZN8MyStructC1Ei
.text$_ZN8MyStructC1EOKS_
.text$_ZN8MyStructC1ERKS_
.text$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.xdata$_ZN8MyStructC1Ei
.xdata$_ZN8MyStructC1EOKS_
.xdata$_ZN8MyStructC1ERKS_
.xdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
MyStruct::MyStruct(int)
MyStruct::MyStruct(MyStruct&&)
MyStruct::MyStruct(MyStruct const&)
std::remove_reference<MyStruct&>::type&& std::move<MyStruct&>(MyStruct&)

So it appears this approach also doesn't work.


However if the target class has a member with explicit move constructor, the implicitly generated move constructor will be visible for the target class. I.e. with this code:

#include <utility>

struct Foobar {
    Foobar() = default;
    Foobar(const Foobar&) = default;
    Foobar(Foobar&&) {}
};

struct MyStruct {
    MyStruct(int x) : x(x) {}
    int x;
    Foobar f;
};
int main() {
    MyStruct s1(4);
    MyStruct s2(s1);
    MyStruct s3(std::move(s1));
    return s1.x+s2.x+s3.x; // make sure nothing is optimized away
}

I will get the symbol for MyStruct's move ctor, but not the copy ctor, as it appears to be fully implicit. I presume the compiler generates a trivial inlined move ctor if it can, and a non-trivial one if it must call other non-trivial move ctors. This still doesn't help me with my quest though.

Aucun commentaire:

Enregistrer un commentaire