Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
210 views
in Technique[技术] by (71.8m points)

c++ - Automatic constructor in explicitly instantiated class template

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 .cpps 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.
  • If I remove extern template from the header, the program compiles.
  • If I use int instead of std::vector<int> for the type of member, the program also compiles.
  • cppreference.com claims that "the compiler will declare a move constructor as a non-explicit inline public member of its class". However, the manually defined 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.)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...