templatesvisual-c++mfcstdmapsafearray

I tried to add a new template helper function to my class and now I get a LNK2001 error. How to fix?


I asked a couple of questions recently on StackOverflow to see if I could consolidate some functions into one by making use of templates. Those questions were:

  1. Can these methods that convert safe arrays into std::list objects be turned into a template function?
  2. Can this template function be adapted to account for the following method?

I had one more function to try and update so I thought I would give it a go myself.

This was the function to update:

void CMSATools::ConvertSAFEARRAY_DATE(SAFEARRAY* psaDates, MeetingDates& rMapMeetingDates)
{
    DATE *pVals = nullptr;
    HRESULT hr = SafeArrayAccessData(psaDates, (void**)&pVals); // direct access to SA memory

    if (SUCCEEDED(hr))
    {
        long lowerBound, upperBound;  // get array bounds
        hr = SafeArrayGetLBound(psaDates, 1, &lowerBound);
        if (FAILED(hr))
            throw _com_error(hr);

        hr = SafeArrayGetUBound(psaDates, 1, &upperBound);
        if (FAILED(hr))
            throw _com_error(hr);

        long cnt_elements = upperBound - lowerBound + 1;
        for (int i = 0; i < cnt_elements; ++i)  // iterate through returned values
        {
            COleDateTime datNotAvailable(pVals[i]);
            DWORD dwDatNotAvailable = EncodeMeetingDate(0, datNotAvailable);
            rMapMeetingDates[dwDatNotAvailable] = datNotAvailable;
        }
        hr = SafeArrayUnaccessData(psaDates);
        if (FAILED(hr))
            throw _com_error(hr);
    }
    else
    {
        throw _com_error(hr);
    }

    hr = SafeArrayDestroy(psaDates);
    if (FAILED(hr))
        throw _com_error(hr);
}

MeetingDates is defined like this:

 using MeetingDates = std::map<DWORD, COleDateTime>;

So I created this helper function:

template<>
void CMSATools::to_push_back(const DATE& rItem, MeetingDates& rItems)
{
    COleDateTime datNotAvailable(rItem);
    DWORD dwDatNotAvailable = EncodeMeetingDate(0, datNotAvailable);
    rItems[dwDatNotAvailable] = datNotAvailable;
}

And I adjusted my calling code like this:

theApp.MSAToolsInterface().ConvertSAFEARRAY<DATE,MeetingDates>(psaDates, mapMeetingDates);

But when I compile this I now get an error:

5>PublishersDatabaseDlg.obj : error LNK2001: unresolved external symbol "public: static void __cdecl CMSATools::ConvertSAFEARRAY<double,class std::map<unsigned long,class ATL::COleDateTime,struct std::less,class std::allocator<struct std::pair<unsigned long const ,class ATL::COleDateTime> > > >(struct tagSAFEARRAY *,class std::map<unsigned long,class ATL::COleDateTime,struct std::less,class std::allocator<struct std::pair<unsigned long const ,class ATL::COleDateTime> > > &)" (??$ConvertSAFEARRAY@NV?$map@KVCOleDateTime@ATL@@U?$less@K@std@@V?$allocator@U?$pair@$$CBKVCOleDateTime@ATL@@@std@@@4@@std@@@CMSATools@@SAXPAUtagSAFEARRAY@@AAV?$map@KVCOleDateTime@ATL@@U?$less@K@std@@V?$allocator@U?$pair@$$CBKVCOleDateTime@ATL@@@std@@@4@@std@@@Z)

What have I done wrong?


My code so far that won't compile when I make the DATE... templated call:

template<typename to>
void CMSATools::to_clear(to& rItems)
{
    rItems.clear();
}

template<typename from, typename to>
void CMSATools::to_push_back(const from& rItem, to& rItems)
{
    rItems.push_back(rItem);
}

template<>
void CMSATools::to_clear(CStringArray& rItems)
{
    rItems.RemoveAll();
}

template<>
void CMSATools::to_push_back(const BSTR& rItem, CStringArray& rItems)
{
    rItems.Add(rItem);
}

template<>
void CMSATools::to_push_back(const DATE& rItem, MeetingDates& rItems)
{
    COleDateTime datNotAvailable(rItem);
    DWORD dwDatNotAvailable = EncodeMeetingDate(0, datNotAvailable);
    rItems[dwDatNotAvailable] = datNotAvailable;
}

template<typename from, typename to>
void CMSATools::ConvertSAFEARRAY(SAFEARRAY* psaItems, to& rItems)
{
    from* pVals = nullptr;
    HRESULT hr = SafeArrayAccessData(psaItems, (void**)&pVals); // direct access to SA memory

    if (SUCCEEDED(hr))
    {
        long lowerBound, upperBound;  // get array bounds
        hr = SafeArrayGetLBound(psaItems, 1, &lowerBound);
        if (FAILED(hr))
            throw _com_error(hr);

        hr = SafeArrayGetUBound(psaItems, 1, &upperBound);
        if (FAILED(hr))
            throw _com_error(hr);

        to_clear<to>(rItems);
        long cnt_elements = upperBound - lowerBound + 1;
        for (int i = 0; i < cnt_elements; ++i)  // iterate through returned values
        {
            to_push_back<from, to>(pVals[i], rItems);
        }
        hr = SafeArrayUnaccessData(psaItems);
        if (FAILED(hr))
            throw _com_error(hr);
    }
    else
    {
        throw _com_error(hr);
    }

    hr = SafeArrayDestroy(psaItems);
    if (FAILED(hr))
        throw _com_error(hr);
}

I this this answer to a similarly titles question:

Error when pass std::map as template template argument

And I think it might apply in my case but I am not sure how to implement it. If indeed it is the reason.


My header class has this snippet in it:


template<typename to>
static void to_clear(to& rItems);
template<typename from, typename to>
static void to_push_back(const from& rItem, to& rItems);
template<>
static void to_clear(CStringArray& rItems);
template<>
static void to_push_back(const BSTR& rItem, CStringArray& rItems);
template<>
static void to_push_back(const DATE& rItem, MeetingDates& rItems);
template<typename from, typename to>
static void ConvertSAFEARRAY(SAFEARRAY* psaItems, to& rItems);
static DWORD EncodeMeetingDate(int iMeetingType, COleDateTime datMeeting);

At the top of my header file I have:

#pragma once
#include "DemoPickerDlg.h"
#include <map>
#include <vector>

#ifdef _WIN64
#import "..\\..\\MSAToolsLibrary\\MSAToolsLibrary\\bin\\x64\\Release\\MSAToolsLibrary.tlb" raw_interfaces_only named_guids
#else
#import "..\\..\\MSAToolsLibrary\\MSAToolsLibrary\\bin\\x86\\Release\\MSAToolsLibrary.tlb" raw_interfaces_only named_guids
#endif

using MeetingDates = std::map<DWORD, COleDateTime>;
using ListDiscussionItems = std::list<MSAToolsLibrary::IDiscussionItemPtr>;
using ListStudentItems = std::list<MSAToolsLibrary::IStudentItemPtr>;
using ListDutyHistoryLookupItems = std::list<MSAToolsLibrary::IDutyAssignmentLookupPtr>;

Solution

  • There is a linked question (Why can templates only be implemented in the header file?) provided to me in the comments. But the answer itself didn't make it clear for me. It was the article that it linked to that helped me understand:

    To quote:

    But in order to understand why things are the way they are, first accept these facts:

    1. A template is not a class or a function. A template is a “pattern” that the compiler uses to generate a family of classes or functions.

    That is what made it clear to me. I moved mode code into the header and it now compiles fine. I found that the easiest resolution.