Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

clang fails to generate defaulted move constructor upon template instantiation

The following code (I couldn't make a shorter MVCE)

unit.h:

#include <vector>

template<typename T>
struct foo
{
    std::vector<T> data;
    foo(foo&&) = default;         // no assembly generated
    foo(std::vector<T>&&v) : data(std::move(v)) {}
};

extern template struct foo<int>;  // indicates template instantiation elsewhere

unit.cc:

#include "unit.h"
template struct foo<int>;         // forces template intantiation 

main.cc:

#include "unit.h"

struct bar
{
    foo<int> f;
    bar(foo<int>&&x) : f(std::move(x)) {}
};

bar makeBar(int x)
{
    std::vector<int> v(x);
    foo<int> f(std::move(v));
    return {std::move(f)};
}

int main()
{
    bar x = makeBar(5);
}

fails to compile under clang (Apple LLVM version 9.0.0 (clang-900.0.39.2) -- which llvm version is that?) with the result:

test> clang++ -std=c++11 -c unit.cc
test> clang++ -std=c++11 -c main.cc
test> clang++ -std=c++11 main.o unit.o
Undefined symbols for architecture x86_64:
  "foo<int>::foo(foo<int>&&)", referenced from:
      bar::bar(foo<int>&&) in main-476e7b.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Everything works fine with gcc (8.2.0). On inspection, it appears that gcc emits foo<int>::foo(foo<int>&&) in main.o, while clang fails to emit it completely.

What is the correct behaviour: should the default move constructor be emitted with unit.o or main.o? Is this a known clang bug?

useful link: https://en.cppreference.com/w/cpp/language/class_template

like image 455
Walter Avatar asked Nov 13 '18 14:11

Walter


1 Answers

This is a clang bug. Your code is well formed so whatever would be the strategy of the compiler considering the "as if" rule, your code should compile.

Explicit instantiation of a class template only instantiates members for which a definition is provided [temp.explicit]/9:

An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is an explicit instantiation definition of only those members that have been defined at the point of instantiation.

Special member function defaulted on their first declaration are only defined when odr-used. So I suppose that the bug is that Clang expects that at the point of explicit instantiation, the defaulted constructor was also instantiated.

So the work around could be first, to declare the move constructor in the header file, then to define it as defaulted in the implementation file:

unit.hpp:

template<typename T>
struct foo
  {
  std::vector<T> data;
  foo(foo&&)=default;
  foo(std::vector<T>&&v) : data(std::move(v)) {}
  };
template<T>
foo<T>::foo(foo&&) noexcept; 
extern template struct foo<int>; 

unit.cpp:

#include <unit.hpp>

template<T>
foo<T>::foo(foo&&) noexcept = default;

template struct foo<int>; //foo(foo&&) has a definition so it is instantiated with the class.

This will force the generation of the definition of the defaulted move constructor (see [dlc.fct.def.default]/5). The draw back is that the definition of foo(foo&&) is not inline anymore.


Alternatively the solution bellow will work:

template<typename T>
struct foo
  {
  std::vector<T> data;
  foo(foo&& o)noexcept:data{move(o.data)}{};
  foo(std::vector<T>&&v) : data(std::move(v)) {}
  };
like image 162
Oliv Avatar answered Nov 14 '22 23:11

Oliv