3

The following code compiles fine, but produces a linker error:

class Base {};

class Derived : public Base {};

template <typename T>
void f(const T& value);

template <>
void f(const Base& value) {
    // ...
}

int main() {
    Base b;
    f(b);

    Derived d;
    f(d); // This line causes linker error.

    return 0;
}

Is it possible to make this code compile and link without adding a duplicate f() specialization for the derived class?

Thank you.

P.S I'm using clang, Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn).

P.P.S The linker error is:

Undefined symbols for architecture x86_64:
  "void f<Derived>(Derived const&)", referenced from:
      _main in test2-4245dc.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Sea Coast of Tibet
  • 5,055
  • 3
  • 26
  • 37
  • Why do you need the template without definition? – eerorika Jan 16 '15 at 16:10
  • Why are you using specialization in the first place? What do you want to do differently depending on the type of the argument? – David G Jan 16 '15 at 16:15
  • 0x499602D2, it's a simplified version of the code, which just reproduces the issue. In the real code there are specializations for many types, but some of them are like `Base` and `Derived` in the example, and I have to duplicate the implementation for linking to work. – Sea Coast of Tibet Jan 16 '15 at 16:17
  • Have you tried `f(d)`? – jepio Jan 16 '15 at 17:11

4 Answers4

3

The linker error occurs because you haven't defined, only declared, the function template

template <typename T>
void f(const T& value);

To avoid it, define the above template and your code will compile, but presumably it still doesn't do what you want.

When calling f with an argument of type Derived the above template is a better match compared to your specialization because the latter requires a derived-to-base conversion while the former doesn't.

You can achieve the behavior you want by using enable_if to allow the first template to participate in overload resolution only if the deduced template argument type is not Base or a type derived from Base.

template <typename T>
typename std::enable_if<!std::is_base_of<Base, T>::value>::type
    f(const T& value) {

}

And change the other f so it's not a specialization, but just an overload.

void f(const Base& value) {
    // ...
}

Live demo

In general, prefer overloading function templates over specialization. Read this to learn about the pitfalls of function template specialization.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Thanks, this works. But if there are more than one Base/Derived pair, for which `f()` has to be specialized/overloaded, is there a way to make `enable_if` take multiple types? – Sea Coast of Tibet Jan 16 '15 at 16:36
  • @SeaCoastofTibet Sure, it's tedious but possible. Change the `enable_if` condition to `!std::is_base_of::value || !std::is_base_of::value || ...`. You can move the ORing to a metafunction to simplify that expression. But is there ever a case where you want the template to be called, or is it always one of the overloads that take a base class reference argument that need to be invoked? If it's the latter, then just get rid of the function template and have an overload per base type you need handled. – Praetorian Jan 16 '15 at 19:30
2

Assuming you really need to have the template and must have all derivatives of Base to call the specialization, you could replace the specialization with an overload and disable the template for all types that derive from Base using type traits:

template <typename T, typename std::enable_if<!std::is_base_of<Base, T>::value>::type>
void f(const T& value);

// no template
void f(const Base& value)

That way only the overload for Base is available to the derived types.

eerorika
  • 232,697
  • 12
  • 197
  • 326
1

The problem is here:

template <typename T>
void f(const T& value);

This function is not defined, however it is the best match for the compiler when you invoke

f(d)

with Derived d;

The Base specialization template<> void f(const Base& value) participates in the overload resolution for f(d), however it is less suitable than the full template template <typename T> void f(const T& value).

One idea is to remove the specialization altogether, define only the full template, and the code will probably do what you want.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • But how can he *"make this code compile and link without adding a duplicate f() specialization [or overload, me] for the derived class?"* – Baum mit Augen Jan 16 '15 at 16:14
  • why problem is here?? – Ankur Jan 16 '15 at 16:14
  • @vsoftco Nope still not working after removing template specialisation – Ankur Jan 16 '15 at 16:19
  • @Shan the code is working, see http://ideone.com/qZLLI5 However one needs to **define** the full template, see the last line of my answer. – vsoftco Jan 16 '15 at 16:19
  • @vsoftco, thanks for your answer. The problem is that the `f()` function is specialized for many types, doing different work for each one, so it's not possible to just put it into a header with full implementation (if I understood your last line correctly). – Sea Coast of Tibet Jan 16 '15 at 16:22
  • @SeaCoastofTibet I understand... maybe try to use traits, see user2079303's answer – vsoftco Jan 16 '15 at 16:22
1

Like the others have said, you haven't defined the generic template function, which is the best candidate for the derived type. If you want to better understand how template function overload resolution works, consult this reference here: http://en.cppreference.com/w/cpp/language/function_template#Function_template_overloading

Your example is a bit contrived (perhaps for brevity's sake). But in general, you should only provide template specializations if you need to implement special logic for a particular type, or if you want to define your template class(es) and function(s) in a separate source file and only need/want to support certain types. Maybe this is what you're actually going for, but generics are called generics for a reason.

Michael B.
  • 331
  • 1
  • 5