Clang не может создать конструктор перемещения по умолчанию при создании шаблона

Следующий код (я не мог сделать более короткий 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);
}

не удается скомпилировать под clang (Apple LLVM версия 9.0.0 (clang-900.0.39.2) - какая версия llvm это?) с результатом:

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)

Все отлично работает с gcc (8.2.0). При проверке выясняется, что gcc испускает foo<int>::foo(foo<int>&&) в main.o, в то время как лязг не может излучать его полностью.

Каково правильное поведение: если default Переместить конструктор будет выпущен с unit.o или же main.o? Это известная ошибка лязга?

полезная ссылка: https://en.cppreference.com/w/cpp/language/class_template

1 ответ

Решение

Это лягушатник Ваш код хорошо сформирован, поэтому, какой бы ни была стратегия компилятора, учитывающая правило "как будто", ваш код должен компилироваться.

Явное создание шаблона класса создает только экземпляры, для которых предусмотрено определение [temp.explicit] / 9:

Явное определение экземпляра, которое называет специализацию шаблона класса, явно создает экземпляр специализации шаблона класса и является явным определением экземпляра только тех членов, которые были определены в момент создания экземпляра.

Специальные функции-члены по умолчанию в их первом объявлении определяются только при использовании odr. Итак, я предполагаю, что ошибка в том, что Clang ожидает, что в момент явного создания экземпляра конструктор по умолчанию также был создан.

Таким образом, обходной путь может быть сначала объявить конструктор перемещения в заголовочном файле, а затем определить его как значение по умолчанию в файле реализации:

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.

Это приведет к генерации определения конструктора перемещения по умолчанию (см. [Dlc.fct.def.default] / 5). Недостатком является то, что определение foo(foo&&) больше не встроен


В качестве альтернативы решение ниже будет работать:

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)) {}
  };
Другие вопросы по тегам