0

My question is very similar to this one, but I'm not sure if the same answers apply here.

I have a templated function whose code is relatively heavy, therefore I am defining it in a separate .cpp file and instantiating it explicitly for the types I need:

foo.h

template <typename T>
void foo(T &arg);

foo.cpp

template <typename T>
void foo(T &arg)
{
... //Long code
}

template
void foo<Type1>(Type1 &arg);

template
void foo<Type2>(Type2 &arg);

Now I would like to also be able to call foo() on Type3 objects, with Type3 a derived class from Type2. I can do this without issues with a non-templated function, but the templated one has linker issues:

class Type3 : public Type2
{ ... }

void foo1(Type2 &arg)
{ ... }

...

Type3 var;
foo1(var); //Runs fine
foo(var); //Linker error

Since I explicitly instantiated foo() to accept Type2 variables, I don't really get why the same conversion from derived to base wouldn't automatically apply as it does for non-templated functions...

I know I can make this code run by writing foo<Type2>(var) or foo((Type2&)var), but would there be a solution that doesn't need to change the way I call the function, and doesn't need explicitly instantiating foo to accept Type3 variables (to avoid increasing compile time)?

  • 3 types? Why is it a template? and not 3 plain overloads? – 463035818_is_not_an_ai Aug 02 '23 at 12:31
  • 1
    *"I don't really get why the same conversion from derived to base wouldn't automatically apply"* Because explicit instantiations don't mean that other types can't be passed as arguments. Moreover, you didn't declare the instantiations in the header, so the compiler has no way to know they exist (though adding them wouldn't change anything). – HolyBlackCat Aug 02 '23 at 12:32
  • Maybe check _static polymorphism_, and the CRTP pattern. That's way better than `foo((Type2&)var)` and still type safe. Affects compile time if that bloats, yes. – πάντα ῥεῖ Aug 02 '23 at 12:33
  • 1
    The only option I see is writing a trait that returns the appropriate base for a type, and have a wrapper template function to cast to that type. Perhaps add `using base = Type2;` to `class Type3`, and read it in your trait if it exists. – HolyBlackCat Aug 02 '23 at 12:34
  • C++ has a seperate compilation model. The compiler is invoked for each .cpp file, and only sees _that one_ .cpp file during compilation. If you call `foo(/* object of type Type3 */)` in the (e.g.) main.cpp file, the compiler (when compiling main.cpp) could not possibly know about the explicit template instantiations in foo.cpp. Besides, even if it could, this is how template argument deduction works. – chrysante Aug 02 '23 at 12:36
  • 2
    `foo()` can accept ***any*** type as its template parameter. Just because type A is derived from type B, passing A to a template function will not deduce B as the template parameter, A gets deduced as the template parameter, not B. If you want to invoke the template instance for B, don't deduce it, but explicitly specify the template parameter. – Sam Varshavchik Aug 02 '23 at 12:41
  • 1
    Thanks for the answers, I understand why what I was trying to do can't work. Since it seems there is no miracle solution that would fit my needs, I'll probably have to rework the whole code differently. – CocoLAsticot Aug 02 '23 at 12:58
  • Of course there's a miracle solution: `foo(var);`, what I wrote. – Sam Varshavchik Aug 02 '23 at 13:02
  • 2
    From shown code, you should have only regular overloads `void foo(Type1&); void foo(Type2 &);` in header. Definitions, in cpp file, might then call the template implementation. – Jarod42 Aug 02 '23 at 13:14
  • @SamVarshavchik yes I'm aware of that solution, but as I wrote in my initial message in an ideal scenario I'd like to avoid that as well. The actual type names are quite long, and I'd like the user of foo to be able to write code transparently for different variable types. – CocoLAsticot Aug 02 '23 at 13:15
  • 1
    Rather than implicit instantiations you can have `void bar(Type1& arg) { foo(arg); }` and similar overloads for the other types. Dont let the user see the template when they dont need to see it – 463035818_is_not_an_ai Aug 02 '23 at 13:27
  • @Jarod42, 463035818-is-not-an-ai I hadn't considered that option, but that actually seems to cover all my needs, so I think I'll go with that, thanks! – CocoLAsticot Aug 02 '23 at 13:41

1 Answers1

4

I have a templated function whose code is relatively heavy, therefore I am defining it in a separate .cpp file and instantiating it explicitly for the types I need:

So just expose overloads for those types:

void foo(Type1&);
void foo(Type2&);

and in source file, forward to your template implementation:

template <typename T>
void fooImpl(T &arg)
{
    // Long code
}

void foo(Type1 &arg) { fooImpl(arg); }
void foo(Type2 &arg) { fooImpl(arg); }
Jarod42
  • 203,559
  • 14
  • 181
  • 302