I have a template<bool VAR> struct Obj
template declared in a header file (obj.h
) with explicit automatic move constructor (= default
).
// obj.h
#pragma once
#include <vector>
template<bool VAR>
struct Obj {
std::vector<int> member;
Obj(int m): member(m) { }
Obj(Obj&&) = default;
int member_fun() const;
};
extern template struct Obj<false>;
extern template struct Obj<true>;
The member function of the template is defined in another file (obj.cpp
) with explicit instantiation of the template:
// obj.cpp
#include "obj.h"
template<bool VAR>
int Obj<VAR>::member_fun() const {
return 42;
}
template struct Obj<false>;
template struct Obj<true>;
This template is then used from the main file (main.cpp
):
// main.cpp
#include <utility>
#include "obj.h"
int main() {
Obj<true> o1(20);
Obj<true> o2(std::move(o1));
return o2.member_fun();
}
The .cpp
s are then compiled and linked together with the following Makefile
:
#CXX=clang++
CXX=g++
CXXFLAGS=-Wall -Wextra -std=c++14
a.out: obj.o main.o
$(CXX) $(CXXFLAGS) $^ -o a.out
obj.o: obj.cpp obj.h
$(CXX) $(CXXFLAGS) -c $< -o $@
main.o: main.cpp obj.h
$(CXX) $(CXXFLAGS) -c $< -o $@
However, I get a linker error: undefined reference to 'Obj<true>::Obj(Obj<true>&&)'
-- the compiler apparently did not instantiate the constructor.
Obj<true>::member_fun()
is defined and the program indeed links successfully if I remove the reference to the move constructor from main.cpp
.extern template
from the header, the program compiles.int
instead of std::vector<int>
for the type of member
, the program also compiles.Obj(int)
constructor is also inline, but it is correctly instantiated.(I received this error with Clang in a project that compiled fine with GCC, so I thought this was a Clang bug. However, when I reduced the problem to this simple case, both GCC 5.4.0 and Clang 3.8.0 produce the same results.)
Template instantiation involves generating a concrete class or function (instance) for a particular combination of template arguments. For example, the compiler generates a class for Array<int> and a different class for Array<double>.
We can use this technique in our template code to create default constructors. For any generic type, T, we can explicitly call its default constructor. Our Measurement template can now be constructed with any type that has a default constructor.
The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation.
To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>(float original); Template arguments may be omitted when the compiler can infer them.
Interesting. I think your code is correct, because:
Your defaulted move-constructor is implicitly inline
because of:
[dcl.fct.def.default]/5:
... A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted.
And [class.mfct]/1:
A member function may be defined ([dcl.fct.def]) in its class definition, in which case it is an inline member function ([dcl.fct.spec])
And thus is exempt from explicit template instantiation according to [temp.explicit]/10 (emphasis mine):
Except for inline functions and variables, declarations with types deduced from their initializer or return value ([dcl.spec.auto]), const variables of literal types, variables of reference types, and class template specializations, explicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer. [ Note: The intent is that an inline function that is the subject of an explicit instantiation declaration will still be implicitly instantiated when odr-used ([basic.def.odr]) so that the body can be considered for inlining, but that no out-of-line copy of the inline function would be generated in the translation unit. — end note ]
In fact, if you try any optimization mode other than -O0
, the problem disappears.
-O0
is a special mode in which inlined functions are not inlined. But it shouldn't matter, the compiler must in that case generate the inline
defaulted move-constructor like it does with the other constructor.
So to me this looks like a compiler bug. Also look at LLVM#22763 and GCC#60796.
I see at least 2 possible workarounds:
Solution 1
Do not use extern template ...
for now (compilation times will suffer but otherwise no big deal).
Solution 2
Trick the compiler into generating the suppressed constructor in obj.cpp
template<> Obj<true>::Obj(Obj&&) noexcept = default;
This one will only be used in -O0
mode. In production code the inlined version will be used instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With