0

I am getting an unresolved external compile error when compiling my template classes. I have separated the code into .h and .cpp files.

I read some posts and now I understand that this will not work due to linking issues as explained in this post.

  1. include the full definition of the member function in the template's header file and not have a source file for the template,

  2. define all the member functions in the template's source file as "inline", or

  3. define the member functions in the template's source with the "export" keyword. Unfortunately this isn't supported by a lot of compilers.

However, none of these options will work since I have conversion functions between a number of these template functions (which results in a cross include compile issue).

How do I fix this issue?

EDIT: Include Code

StaticArray.h

template<typename T>
class Array;

template<typename T, unsigned int N>
class StaticArray
{
protected:
    T* _data[N];
public:
    StaticArray();
    StaticArray(const StaticArray<T,N>& other);
    ~StaticArray();

    void Release(unsigned int index);
    void Release(T* data);
    void ReleaseAll();

    Array<T> ToArray();

    bool operator == (const StaticArray<T,N>& other);
    bool operator != (const StaticArray<T,N>& other);
    T*& operator [] (unsigned int index) const;
    StaticArray<T,N>& operator = (const StaticArray<T,N>& other);
};

StaticArray.cpp

#pragma region StaticArray::CoreMethods

template<typename T, unsigned int N>
StaticArray<T, N>::StaticArray()
{
    for (unsigned int i = 0; i < N; i++)
    {
        this->_data[i] = null;
    }
}

template<typename T, unsigned int N>
StaticArray<T, N>::StaticArray(const StaticArray<T,N>& other)
{
    for (unsigned int i = 0; i < N; i++)
    {
        this->_data[i] = other._data[i];
    }
}

template<typename T, unsigned int N>
StaticArray<T, N>::~StaticArray()
{

}

#pragma endregion

#pragma region StaticArray::Methods

template<typename T, unsigned int N>
void StaticArray<T,N>::Release(unsigned int index)
{
    if (index < N)
    {
        delete this->_data[i];
        this->_data[i] = null;
    }
    else
    {
        throw new Exception::IndexOutOfBoundsException("StaticArray accessed at index greater than N.");
    }
}

template<typename T, unsigned int N>
void StaticArray<T,N>::Release(T* data)
{
    if (data == null)
    {
        throw new Exception::InvalidArgumentException("StaticArray Release call argument must not be null.");
    }

    for (unsigned int i = 0; i < N; i++)
    {
        if (this->_data[i] == data)
        {
            this->Release(i);
            return;
        }
    }

    throw new Exception::InvalidArgumentException("StaticArray Release call argument was not in the array.");
}

template<typename T, unsigned int N>
void StaticArray<T,N>::ReleaseAll()
{
    for (unsigned int i = 0; i < N; i++)
    {
        if (this->_data[i] != null)
        {
            delete this->_data[i];
            this->_data[i] = null;
        }
    }
}

template<typename T, unsigned int N>
Array<T> StaticArray<T,N>::ToArray()
{
    Array<T> ret(N);

    for (unsigned int i = 0; i < N; i++)
    {
        ret[i] = this->_data[i];
    }

    return ret;
}

#pragma endregion

#pragma region StaticArray::OperatorOverloads

template<typename T, unsigned int N>
bool StaticArray<T,N>::operator == (const StaticArray<T,N>& other)
{
    for (unsigned int i = 0; i < N; i++)
    {
        if (this->_data[i] != other._data[i])
        {
            return false;
        }
    }

    return true;
}

template<typename T, unsigned int N>
bool StaticArray<T,N>::operator != (const StaticArray<T,N>& other)
{
    return !((*this) == other);
}

template<typename T, unsigned int N>
T*& StaticArray<T, N>::operator[](unsigned int index) const
{
    if (index < N)
    {
        return this->_data[index];
    }
    else
    {
        throw new Exception::IndexOutOfBoundsException("StaticArray accessed at index greater than N.");
    }
}

template<typename T, unsigned int N>
StaticArray<T, N>& StaticArray<T, N>::operator = (const StaticArray<T,N>& other)
{
    for (unsigned int i = 0; i < N; i++)
    {
        this->_data[i] = other._data[i];
    }
    return *this;
}

main.cpp

#include "StaticArray.h"
#include "Array.h"

int main(int argc, char** argv[])
{
    StaticArray<int,5> sar;

    sar[0] = new int(1);
    sar[1] = new int(2);
    sar[2] = new int(3);
    sar[3] = new int(4);
    sar[4] = new int(5);

    return 0;
}
Community
  • 1
  • 1
Sellorio
  • 1,806
  • 1
  • 16
  • 32
  • @close-vote-reviewers: This has been flagged as duplicate of one of the general "undefined reference when using template" questions, but **it isn't a duplicate** of that. – jogojapan Jun 03 '13 at 05:51

4 Answers4

0

Explicit template instantiation. Add this line to the bottom of StaticArray.cpp:

template class StaticArray<int,5>;
  • Be aware that your StaticArray class had several compile errors that I had to fix once I added this line. I did eventually get everything to compile (in VS 2010). You may think your StaticArray class was complete and compile-error-free, but it was never really getting compiled until something instantiated it. My original answer was merely to address the "unresolved external" linker error. – Eric Undersander Jun 03 '13 at 04:52
  • Ok I got it (tried to put it in main but that didn't work). But it would not be viable to specialise every template I will use for this class. Is there no alternative to this? Or way of doing this more generically? – Sellorio Jun 03 '13 at 04:58
  • I agree that my solution is not viable if you're planning to use StaticArray with lots of different types. I believe the common solution is your item #1 above--put your whole StaticArray class in the header, with all methods being inline. Another idea would be to add a new source file, StaticArrayInstantiations.cpp, where all these explicit instantiations live... that is perhaps a bit cleaner than adding instantiations to StaticArray.cpp. – Eric Undersander Jun 03 '13 at 05:04
  • I can't put it all in the .H file since then I can't use my conversion function (as explained above). I agree that a separate .cpp file with template specifications is a better option that having it in the main cpp file but neither is an option to be honest. Do you have any way to make this work properly? – Sellorio Jun 03 '13 at 05:50
0

I see the forward declaration for class Array at the beginning of StaticArray.h. In your main.cpp try swapping the lines to

#include "Array.h"
#include "StaticArray.h"

from a quick glance it seems like the compiler hasnt seen Array when its building StaticArray and hence unresolved external?

salek
  • 444
  • 4
  • 14
  • It had no effect. Plus the whole idea of separating the .h and .cpp is that order is irrelevant. – Sellorio Jun 03 '13 at 04:48
  • paste the declaration+definitions for Array class. And This is a linker Error you are complaining about. Since you have 2 cpp files there are 2 object files generated. And the linker is not finding StaticArray's functions to link to main(). If you include #include "StaticArray.cpp" just above your main, the linker error would go away. – salek Jun 03 '13 at 10:12
  • or you can include the static array cpp in your staticarray.h see this gist, I tried to come up on about the Array Class.... anyways this builds but you have a lot to fix in this code, rather have the constructor passed the length of the array which it can dynamically allocate instead of passing it as templte parameter https://gist.github.com/wannado/5697403 – salek Jun 03 '13 at 10:52
0

I eventually managed to figure out the best way to make this functionality work in a structured manner.

You need to remove the conversions (such as StaticArray to Array) from the StaticArray class and instead have both StaticArray and Array included in another code file that contains utility functions (such as conversions).

The key here is to keep the includes working upwards. That is: utility.h (for example) includes Array.h and StaticArray.h instead of them including one another.

Then you are free to put all definition code into the StaticArray.h file with no cross-include side-effects (and thus fix the unresolved external).

Sellorio
  • 1,806
  • 1
  • 16
  • 32
0

You don't have to have a definition of class Array to use class StaticArray except at the point you actually call ToArray. So it doesn't matter in which order the templates and their functions are declared, just as long as each one forward-declares the classes it uses.

Neil
  • 54,642
  • 8
  • 60
  • 72
  • Since I am accessing the operator [] for the Array, I do need Array declared fully. – Sellorio Jun 03 '13 at 23:00
  • @MrUniverse But only in the source file where you actually call `ToArray`, not in the StaticArray header where the `ToArray` method is defined. – Neil Jun 04 '13 at 11:50
  • Ah it works. And I think I know why. The compiler only really generates the class when you specify `Array` for example. Thanks for the help. – Sellorio Jun 04 '13 at 22:47
  • @MrUniverse Exactly. Note that it's still possible to trip up, for example when your template class is itself derived from a template class. – Neil Jun 04 '13 at 23:32