clang fails to generate defaulted move constructor upon template instantiation
Asked Answered
M

1

8

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

Mystify answered 13/11, 2018 at 14:17 Comment(1)
Latest XCode is 10, so it's probably LLVM 5 or perhaps 6.Floury
L
6

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)) {}
  };
Lurid answered 13/11, 2018 at 15:48 Comment(10)
It is indeed a clang bug. I filed a report and learned that it was already fixed in version 6 (apparently w/o a bug report). As to where the explicitly defaulted members are to be exported, I'm still not 100% sure.Mystify
Your workaround works (good), but only provides the (otherwise missing) constructor for the particular instantiation, i.e. for foo<int>. The code will fail to compile with any other foo<T> (very bad). There appears to be no work around, other than avoiding to mix explicitly defaulted member functions with explicit template instantiation.Mystify
@Mystify OK I know how to fix that too.Lurid
@Mystify ... Indeed it work with gcc but not with clang! That is an other bug of clang!Lurid
@Mystify Because the declaration of the specialization of the member appears before the instantiation declaration. Clang should not try to instantiate it.Lurid
@Mystify temp.explicitLurid
The clang errors don't refer to the instantiation, but complain about error: definition of explicitly defaulted move constructor (both for the explicit declaration in unit.h and definition in unit.cc)Mystify
@Mystify Indeed, clang sometime fails to recognize member specialization. It almost fails every time the specialization is a member of a partial specialization, here this is worst. I revert to the preceding solution since whatsoever this one is broken for clang.Lurid
@Mystify I added an other option.Lurid
your latest 'solution' does not initialize the member data though.Mystify

© 2022 - 2024 — McMap. All rights reserved.