stix
stix

Reputation: 1146

What is the proper way to define a templated class's member function when behavior is identical for template types?

Figuring if something wasn't broke, I'd break it, I decided to specialize a class I had so that it could be templated between float and double precision automagically.

I have the following [simplified] class declaration:

// Quatcam.h
#pragma once
#include <boost/math/quaternion.hpp>
#include <boost/numeric/ublas/matrix.hpp>
template<typename FloatType>
class QuaternionCamera
{
public:
    QuaternionCamera();
    void applyTranslation(boost::numeric::ublas::vector<FloatType> translationVector);
    boost::numeric::ublas::matrix<FloatType> getTranslationMatrix();

protected:
    boost::numeric::ublas::vector<FloatType> m_location;
    boost::math::quaternion<FloatType> m_orientation;

};

I have defined the member functions in a .cpp file:

//Quatcam.cpp
#include "Quatcam.h"

using namespace boost::numeric::ublas;

template<typename FloatType>
QuaternionCamera<FloatType>::QuaternionCamera()
    : m_location(3),
    m_orientation(1,0,0,0)
{
    m_location[0] = m_location[1] = m_location[2] = 0;
}

template<typename FloatType>
void QuaternionCamera<FloatType>::applyTranslation(boost::numeric::ublas::vector<FloatType> translationVector)
{
    m_location += translationVector;

}
template<typename FloatType>
boost::numeric::ublas::matrix<FloatType> QuaternionCamera<FloatType>::getTranslationMatrix()
{
    boost::numeric::ublas::matrix<FloatType> returnMatrix = boost::numeric::ublas::identity_matrix<FloatType>(4,4);

    boost::numeric::ublas::vector<FloatType> invTrans = -m_location;
    returnMatrix(3,0) = invTrans[0];
    returnMatrix(3,1) = invTrans[1];
    returnMatrix(3,2) = invTrans[2];
    return returnMatrix;

}

This code by itself will happily compile into a .lib or .obj file, but attempting to use the class in situ results in linker errors. Here is my example main.cpp attempting to use the class:

#include "Quatcam.h"
#include <boost/numeric/ublas/io.hpp>
#include <iostream>

int main(int argc, char** argv)
{
    QuaternionCamera<float> qcam;

    boost::numeric::ublas::vector<float> loc(3);

    loc[0] = 0;
    loc[1] = 5;
    loc[2] = 0;
    qcam.applyTranslation(loc);

    boost::numeric::ublas::matrix<float> qtm = qcam.getTranslationMatrix();
    std::cout << "qtm: "<< qtm << std::endl;
    return 0;

}

This code fails to link with an error for missing symbols for getTranslationMatrix and applyTranslation. I assume this is because I haven't technically specified a full specialization of the functions for the type float.


Question(s)

Given that the behavior is the same for any atomic input type (float, double, even int, etc...) and only affects the precision of the answers.

Is there a way to force the compiler to emit specializations for all of them without having to;

  1. move all of the function definitions into the header file, or;
  2. explicitly create specializations for all data types that would presumably involve a lot of copypasta?

Upvotes: 0

Views: 107

Answers (2)

aedm
aedm

Reputation: 6574

You should put these functions into the header file, not into the .cpp source.

The compiler only creates function instantiations after the template argument deduction is complete. The resulting object file will contain a compiled function for each type that the template was used with.

However, .cpp files are compiled separately. So, when you compile Quatcam.cpp, the compiler doesn't find any instantiations for this type, and doesn't create a function body. This is why you end up with a linker error.

To put it simply, this is how your header should look like:

template<typename T>
class Foo {
    void Print();
    T data;
};

// If template arguments are specified, function body goes to .cpp
template<>
void Foo<float>::Print();

// Template arguments are incomplete, function body should remain in the header
template<typename T>
void Foo<T>::Print() {
    std::cout << data;
}

And this should to the .cpp source:

template<>
void Foo<float>::Print() {
    std::cout << floor(data);
}

Upvotes: 0

Filip Ros&#233;en
Filip Ros&#233;en

Reputation: 63797

Recommended links


Recommended Practice

Instead of moving the definitions from the .cpp to the header, rename the .cpp to .tpp and add #include "Quatcam.tpp" at the end of Quatcam.h.

This is how you typically split up the template declarations, and their definitions, while still having the definitions available for instantiation.

Note: If you follow this road, you should not compile the .tpp by itself, as you were doing with the .cpp.


Explicit Instantiation

You can explicitly instantiate the templates in question in your .cpp to provide them for the linker, but that requires that you know the exact types that you'd require an instantation of.

This means that if you only explicitly instantiate QuaternionCamera<float>, you'd still get a linker error if main.cpp tries to use QuaternionCamera<double>.

There's no way of forcing instantiation of all "atomic input types", you'll have to write them all out explicitly.

 template class QuaternionCamera<float>;  // explicit instantiation
 template class QuaternionCamera<double>; // etc, etc...

Upvotes: 3

Related Questions