1

Edit: This is not a duplicate of the linked question since I am using explicit instantiation and only a specific type of member functions do not link (others do).

The following code compiles but doesn't link and I don't understand why. It is explicitly instantiating the Vector class to limit the number of possible arguments for T and therefore hides the definition of Vector<T> in a .cpp file.

// fwd_decl.hpp
#pragma once
template<typename T>
struct Vector; // Forward declare Vector to be used in other headers

// Vector.hpp
#pragma once
#include "fwd_decl.hpp"

template<typename T>
struct Vector
{
    template<typename U> // To allow for other types than T to be used
    Vector operator+(const Vector<U> & other) const;
    T x;
    T y;

    // more stuff..
};

// Vector.cpp
#include "Vector.hpp"
template<typename T>
template<typename U>
Vector<T> Vector<T>::operator+(const Vector<U> & other) const
{
    return { static_cast<T>(x + other.x), static_cast<T>(y + other.y) };
}
template struct Vector<int>; // Explicitly instantiate Vector<T> with int

// main.cpp
#include "Vector.hpp"
int main()
{
    Vector<int> b = Vector<int>{ 2, 3 } + Vector<int>{ 4, 5 };
}

The error I'm getting is:

1>main.obj : error LNK2001: unresolved external symbol "public: struct Vector<int> __thiscall Vector<int>::operator+<int>(struct Vector<int> const &)const " (??$?HH@?$Vector@H@@QBE?AU0@ABU0@@Z)

I'm compiling with VC++ 17 in VS 15.9.4.

Note that calls to members of Vector<int> that are not function templates do link normally.

Jupiter
  • 1,421
  • 2
  • 12
  • 31
  • 2
    You should use the explicit instantion of the `Vector::operator+` method (for all possible pairs of `T` and `U`) in addition to the explicit instantion of the `Vector` class. Also you may simply move the definition of the `Vector::operator+` method to the header file. – Constructor Jan 07 '19 at 20:43
  • Ah yes you are right. Add it as an answer and I'll accept it. – Jupiter Jan 07 '19 at 21:04
  • I can't do it due to the fact your question is marked duplicate (no answers can be added in this case). – Constructor Jan 08 '19 at 07:50
  • 1
    I see now where the comment on my old answer came from. You need an explicit instantiation declaration in your header: `extern template struct Vector;` - the definition in a CPP file is not enough if you want to reap the benefits of the explicit instantiation here. – StoryTeller - Unslander Monica Jan 08 '19 at 08:14
  • @StoryTeller If all class members are defined in cpp file (as in such case) `extern template struct Vector;` directive makes sense only for compiler-generated methods, doesn't it? – Constructor Jan 08 '19 at 11:13
  • @Constructor - A compiler will implicitly instantiate the whole class in every TU if that declaration is not present. I don't recall off the bat if it's problematic in terms of the ODR, but what I know for sure is that telling the compiler "don't bother, it's elsewhere" is better than having it do work it has to consolidate later for nothing. – StoryTeller - Unslander Monica Jan 08 '19 at 13:00
  • @StoryTeller Well, C++14 removed the extern keyword for this use anyway but you actually would gain the benefits from explicit instantiation without using extern for this (given that the definition of all members are in a single separate TU). The main difference is that, without extern, the linker will not be able to find the definition if there is no explicit instantiation for the template argument and give an error (with extern and the definitions in the hpp file it would simply implicitly instantiate your template). – Jupiter Jan 08 '19 at 17:54
  • @Jupiter - I don't know where you are getting this falsehood from, but it's certainly not a place like [cppreference](https://en.cppreference.com/w/cpp/language/class_template). It's not removed in C++14 or in any revision. – StoryTeller - Unslander Monica Jan 08 '19 at 17:59
  • Yes I saw it on cpp reference. I was mistaken. It's export that was removed (https://en.cppreference.com/w/cpp/language/class_template) – Jupiter Jan 08 '19 at 18:01

1 Answers1

2

You should use the explicit instantion of the method template<typename T> template<typename U> Vector<T> Vector<T>::operator+(const Vector<U> & other) const (for all possible pairs of T and U) in addition to the explicit instantion of the Vector<T> class:

template Vector<int> Vector<int>::operator+(const Vector<short> & other) const;

Also you may simply move the definition of the Vector<T>::operator+ method to the header file.

In C++11 the extern template directive was introduced. You may use it in the header file for Vector<T> class (as @StoryTeller suggested in the comments):

extern template struct Vector<int>;

...to prevent the compiler from instantiating the Vector<T> class in every translation unit its specializations are used. Of course the same extern template directives may also be used for all Vector<T>::operator+ specializations explicitly instantiated in the .cpp file.

Constructor
  • 7,273
  • 2
  • 24
  • 66