Explicit template specialization - multiple definitions

2.6k views Asked by At

I have done explicit specializations before, I just can't figure out why this does not work:

StringUtils.hpp

#ifndef AYC_STRINGUTILS_HPP
#define AYC_STRINGUTILS_HPP

#include <string>

class StringUtils {
public:
    template <typename T>
    static std::string toString(const T& t);
};

#include "StringUtils.tpp"

#endif //AYC_STRINGUTILS_HPP

StringUtils.tpp

#include "StringUtils.hpp"

template<typename T>
std::string StringUtils::toString(const T& t) {
    return std::to_string(t);
}

template<>
std::string StringUtils::toString<std::string>(const std::string& t) {
    return t;
}

The errors I get are linker errors complaining about multiple definitions of the function toString.

Many files in the project use #include "StringUtils.hpp".

How can I try to fix this error? Is something wrong in the class StringUtils?

3

There are 3 answers

3
R Sahu On BEST ANSWER

In addition to the solution provided in the answer by Brian, you can declare the specialization in the .hpp/.tpp file and define it in a .cpp file.

StringUtils.hpp file:

#ifndef AYC_STRINGUTILS_HPP
#define AYC_STRINGUTILS_HPP

#include <string>

class StringUtils {
public:
    template <typename T>
    static std::string toString(const T& t);
};

// Generic implementation.
template<typename T>
std::string StringUtils::toString(const T& t) {
    return std::to_string(t);
}

// Declation of the specialization.
template<>
std::string StringUtils::toString<std::string>(const std::string& t);

#endif //AYC_STRINGUTILS_HPP

StringUtils.cpp file:

#include "StringUtils.hpp"

// Definition of the specialization.
template<>
std::string StringUtils::toString<std::string>(const std::string& t) {
    return t;
}

Test program:

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

int main()
{
   std::string a("test");
   std::cout << StringUtils::toString(a) << std::endl;
   std::cout << StringUtils::toString(10) << std::endl;
}

Output of the test program:

test
10
2
Brian Bi On

An explicit (full) specialization of a function template is subject to the one-definition rule, so StringUtils::toString<std::string> must not be defined in multiple translation units. You can solve this problem by declaring it inline.

0
Yakk - Adam Nevraumont On

Template function specialization is almost always the wrong answer.

Classes are poor namespaces.

Simply overload instead of specialize.

namespace StringUtils {
  template <typename T>
  std::string toString(const T& t){
    using std::to_string;
    return to_string(t);
  }
  inline std::string toString(std::string s){ return std::move(s); }
}

overload resolution does what you want, and it permits efficient signature variation (like above, where I take s by-value, which could avoid an extra heap allocation).

Also note I enabled ADL extension of to_string for custom classes. Simply overload to_steing(X) in X's namespace and StringUtils::toString(X) finds it.


Your immediate problem is you need to mark the specialization inline.