1

Apologies if this has been asked and answered before, but I've been searching for an understandable solution now for hours with no joy.

I'm trying to implement a simple class hierarchy with operator overloads defined within the base class (since these will not differ between the various derived classes). However, since most of these will need to return a new object of whatever derived class we're in the context of, I'm assuming they need to be declared as a templated method, however, when I attempt to compile doing this, the linker gives me an 'Unresolved external symbol' error ...

By way of an example:

/// This is in the class header file
class Base
{
   // Declare all the base storage and methods ...

public:
   template<class T>
   friend T operator+(const T& lhs, const T& rhs);

   // .... other stuff in here.
}


/// Then in the implementation file
T operator+ (const T& lhs, const T& rhs)
{
   return T(lhs->m_1 + rhs->m_1, lhs->m_2);
}

I was hoping that this would lead to my being able to declare derived objects thus:

class Derived : public Base
{
   // ... Add the derived class functionality

   // -- Question:  Do I need to do anything in here with operator+ ???
}
Derived A();
Derived B();

Derived C = A + B;

Is the desired outcome, but short of defining the operators within each individual derived class, I cannot see a way I can implement this in C++ since the Template approach leads to the linker error.

Am I missing something blindingly obvious and fundamental, or is there simply no easy way of doing this in C++?

Mark Edwards
  • 108
  • 7
  • 1
    you are befriending `operator+(const T& lhs, const T& rhs)` for any `T`, not sure if thats a good idea. Among other things this opens a door for anybody to access private members. Anyhow, I think this is your immediate problem: https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file – 463035818_is_not_an_ai May 27 '20 at 19:39
  • That was indeed the issue. Note also the answer from Human-Compiler below that also outlines how to make the operator type-safe. It's been years since I did anything even remotely serious with C or C++ so it's all like trying to learn a foreign language from scratch again ... – Mark Edwards May 27 '20 at 19:55

1 Answers1

2

Your comment indicates "in the implementation file" for the template, which is likely the cause of your issue. Function template declarations (e.g. T operator+(const T&, const&); declare the symbols that must be linked against -- but it requires an instantiation of that template somewhere.

Simply defining the template function in the source file doesn't actually instantiate the code -- it requires either concrete instantiations of each type explicitly that is desired to be linked against, or to have the function template definition visible from the place that intends to call it (see this answer for more detail).

In many cases, it's better to define the function template in a header file, so that any callers of the template will be able to instantiate the functions without requiring linking against an existing instantiation elsewhere.


That said...

You might want to re-think your current approach. Your operator+ template does not constrain what type T can be considered, which will cause operator+(const T&, const T&) to be a viable overload for any T type that does not already have an operator+ provided that the declaration is visible during overload resolution. This may lead to other strange compiler / linker errors.

There are several ways to address constraining the type; probably the simplest one would be to use SFINAE to constrain it by checking that T is derived from Base.

For example:

/// This is in the class header file
class Base
{
   // Declare all the base storage and methods ...

public:
   template<class T, class>
   friend T operator+(const T& lhs, const T& rhs);

   // .... other stuff in here.
}


// Note: In the same header!
// This only enables + if 'T' derives from 'Base'
template <typename T, typename = std::enable_if_t<std::is_base_of<Base,T>::value>>
T operator+(const T& lhs, const T& rhs)
{
   return T(lhs->m_1 + rhs->m_1, lhs->m_2);
}
Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
  • Many thanks. That was indeed the issue. Thanks also for the pointer on type-checking the operators. That was the next item on my agenda once I'd got the basic functionality working :) – Mark Edwards May 27 '20 at 19:57