c++c++14variadic-templatesperfect-forwardingexplicit-instantiation

Perfect forwarding fails to link with explicit template instantiation directives (EIDir; a.k.a. manual instantiation)


The question is why does the linker fail (g++, ver. 7.5, Ubuntu 18.4) to perfect forward a constructor of a template class, when the constructor definition is hidden in the implementation file (*.cpp) but explicitly instantiated in the main.cpp?

Specifically: fwd_machine.hpp:

#include <memory>
#include <stdexcept>

template<typename T>
struct some_type
{
 some_type(int, void*); // ld error: undefined reference to `some_type<int>::some_type(int, void*)'

 // if no EIDir, i.e., defined in the header,
 // then it works fine:
 //
 //some_type(int, void*){}
 //
 // or:
 // if explicit specialization used (with pass-through version in the header)
 // then it also works fine:
 //
 // some_type(int, void*){
 //   throw std::runtime_error("Error: only specializations whould be called...");
 // }
 // okay
};

struct factory_t
{
 template<typename some_t, typename... Args>
 decltype(auto)
 operator()(Args&&... args)
 {
   return std::make_unique<some_t>(some_t(std::forward<Args>(args)...));
 }
};

template<typename FType, typename...Fargs>
constexpr
decltype(auto) launch(FType f, Fargs&&... args)
{
 return f.template operator()<some_type<int>>(std::forward<Fargs>(args)...);
}

where the launch() function template runs a generic functor taking variadic args by r-value so that they can be forwarded. In this case the constructor of some_type (by making a unique_ptr out of it).

However, I need the constructor definition hidden in the implementation file: fwd_machine.cpp:

#include "fwd_machine.hpp"

// if no EIDir, i.e., defined in the header,
// then it works fine...

// or:
// if explicit specialization used (with pass-through version in the header)
// then it also works fine:
//
//template<>
//some_type<int>::some_type(int, void*){} // okay

template<typename T>
some_type<T>::some_type(int, void*){} // ld error: undefined reference to `some_type<int>::some_type(int, void*)'

and then in the main, I use manual instantiation for the whole class: main_fwd_issues.cpp:

#include <iostream>
#include "fwd_machine.hpp"

// g++ -std=c++14 fwd_machine.cpp main_fwd_issues.cpp -o mfi.exe

// if no EIDir or specialization, then it works fine...
//
template class some_type<int>;

int main(void)
{
  int i{1};
  void* p{nullptr};

  auto uniq_p = launch(factory_t{}, i, p);

  std::cout<<"Finishing up...\n";
  return 0;
}

But, as mentioned, I get linker error: undefined reference to `some_type<int>::some_type(int, void*)'

The only solutions I found are to either use automatic template instantiation (i.e., provide the definition of the constructor in the header); or, to use explicit specialization (define in the header a generic constructor, which is not to be called, and specialize for <int> in the implementation file). Anything else I tried (qualifying the signature of the constructor; not using perfect forwarding in either launch() or factory_t::operator()) failed.

I would like to understand why the linker fails to use the manual instantiation of some_type, and if there still might be a way to use manual instantiation (and avoid specializations).


Solution

  • Class template @ cppreference

    The complete definition must appear before the explicit instantiation of a class template

    This is not the case in your program when you try to instantiate it in main.cpp.

    The correct way to explicitly instantiate some_type<int> would be to add

    // in fwd_machine.hpp
    extern template class some_type<int>;
    
    // in fwd_machine.cpp (after the definition is complete):
    template class some_type<int>;