1

I have an overloaded function, their definitions are the same and only one parameter change. So, I tried to create a template function but when I call it I got a symbol lookup error.

Header file

void func(Myobject obj);
void func(MySecondObject obj);

Source file

template<typename T>
void func(T obj) { obj.foo(); }

Test file

#include "MyHeaderFile.h"

func(MySecondObject()); // symbol lookup error at runtime

Thanks.

  • 1
    That's simply not how it works. You cannot implement two different overloaded functions by only providing a function template of the same name. – Max Langhof Dec 13 '19 at 14:30
  • @Evg Doesn't really fit here imo. This question involves a misconception of overloading vs templates. – Max Langhof Dec 13 '19 at 14:31
  • You should define template specialization in the source file. – yadhu Dec 13 '19 at 14:32
  • Sorry guys but that dupe is simply not right. It would only fit if the header file contained a template declaration, but it does not. The intention here is different. – Max Langhof Dec 13 '19 at 14:33

3 Answers3

6

If you say

void func(Myobject obj);
void func(MySecondObject obj);

then you promise to the compiler that it will eventually find

void func(Myobject obj)
{ /* implementation */ } 

void func(MySecondObject obj)
{ /* implementation */ }

Since you didn't provide these implementations (there are no definitions for these symbols you declared), you get an error.

What you can do however is this:

Header file

void func(Myobject obj);
void func(MySecondObject obj);

Source file

template<typename T>
void func_impl(T obj) { obj.foo(); }

void func(Myobject obj) { func_impl(obj); }
void func(MySecondObject obj) { func_impl(obj); }

This allows you to declare and define "real" (non-templated) functions for your users, but you can still implement all of them by delegating the work to the same template function. It is best practice to put the func_impl into an unnamed namespace (namespace /* no name here */ { /* code goes here */ }) which will make it internal to that translation unit (and also makes it clear that it's not intended to be seen/used by other code, without having to cross-reference the header).


(The following is already discussed at Why can templates only be implemented in the header file?).

An alternative approach is to declare the function template in the header, then define it in the source file and provide explicit instantiations for the types you want:

Header file

template<class T>
void func(T obj);

// Explicit instantiation declarations
extern template void func<Myobject>(Myobject obj);
extern template void func<MySecondObject>(MySecondObject obj);

Source file

template<typename T>
void func(T obj) { obj.foo(); }

// Explicit instantiation definitions
template void func<Myobject>(Myobject obj);
template void func<MySecondObject>(MySecondObject obj);

This approach is significantly more verbose, potentially confusing for users that are not template-affine, and trying to use it with the wrong type leads to linker errors instead of a nicer compiler error, so the first solution above can easily be the most appropriate.

Or you could define the entire template in the header file. There can be good reasons to avoid that though.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • 1
    I was just gonna write what basically is this answer. I would suggest that the template to generate the implementation in the source file be in an unnamed namespace to make it internal to the translation unit… – Michael Kenzel Dec 13 '19 at 14:37
  • 2
    @KonradRudolph Technically, function templates are not implicitly inline but they are exempt from the ODR the same way inline functions are [\[basic.def.odr\]/6](https://timsong-cpp.github.io/cppwp/n4659/basic.def.odr#6). Granted, the chance that there ever happens to be another definition of a function template with the same signature in some other translation unit will be pretty small in practise. But if there ever is, your program will have undefined behavior… – Michael Kenzel Dec 13 '19 at 14:59
  • @MichaelKenzel Fair enough. The situation you describe re: ODR is 100% the same between inline functions and function templates though. And I agree that for both it makes sense to use unnamed namespaces to avoid the risk of name collision. In hindsight my previous comment is unnecessary. – Konrad Rudolph Dec 13 '19 at 15:52
1

It seems that you want to have a template function with two explicit instantiations for MyObject and MySecondObject. Template function declaration should be in your header like that:

template<typename T>
void func(T obj);

Then in the corresponding source file you should provide generic implementation + explicit instantiations:

#include "header.hpp"

template<typename T>
void func(T obj)
{
  obj.foo();
}

template void func<MyObject>(MyObject obj);
template void func<MySecondObject>(MySecondObject obj);

Then you can use that two versions in your code:

func(MyObject());
func(MySecondObject());

but call to func with another template parameter, e.g. func('c') will result in undefined reference.

cpp reference section "Explicit instantiation".

pptaszni
  • 5,591
  • 5
  • 27
  • 43
  • 1
    Note that declaring this function template in the header potentially changes the meaning of a program. You are adding an unconstrained signature that matches not only the two original signatures, but basically anything. It's just missing a definition for all other cases… – Michael Kenzel Dec 13 '19 at 15:06
  • @MichaelKenzelm that's true, answer of Max Langhof is more robust – pptaszni Dec 13 '19 at 15:11
1

I would suggest defining the template specializations in the source file. It hides the template specializations in the current translation unit.

MyHeaderFile.h

template <typename T>
void func(T obj);

MyHeader.cpp

#include <iostream>
#include "MyHeaderFile.h"

template <typename T>
void func(T obj)
{
  std::cout << "function name: " << __func__;
}

template void func<MySecondObject>(MySecondObject obj);

template void func<Myobject>(Myobject obj);

I hope this answer solves your problem.

yadhu
  • 1,253
  • 14
  • 25
  • Note that declaring this function template in the header potentially changes the meaning of a program. You are adding an unconstrained signature that matches not only the two original signatures, but basically anything. It's just missing a definition for all other cases… – Michael Kenzel Dec 13 '19 at 15:06
  • @MichaelKenzel Could that be mitigated by something like: `template||std::is_same_v, int> = 0> void func(T);` in the header file? It'd give compilation errors for unsupported types instead of linking errors too, which is nice I guess. – Ted Lyngmo Dec 13 '19 at 15:10
  • 1
    @TedLyngmo Yes, except that SFINAE has strong compilation time impact and (in this case) can be "broken" by specifying explicit template arguments. Probably not downsides worth caring about, but I don't see the upside either. It's not like the code becomes more readable or user-friendly. – Max Langhof Dec 13 '19 at 15:13
  • @MaxLanghof True. I've opted for explicit instantiation in the header file myself in the past (the few times it's been called for), but was thinking about this answers approach too. – Ted Lyngmo Dec 13 '19 at 15:18