0

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>;
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 1
    The linked Q&A is about a compiler error. You have a linker error. Those are unrelated. Your issue has an answer in [this Q&A](https://stackoverflow.com/q/495021/1889329). – IInspectable Jan 06 '21 at 14:04
  • @Ilnspectable what am I looking for in those 17 answers? I include the – Andrew Truckle Jan 06 '21 at 16:10
  • 1
    @AndrewTruckle Cannot duplicate. The code builds without errors in a boilerplate MFC console app after adding `#include `, `#include ` and a dummy `DWORD EncodeMeetingDate(int n, COleDateTime& dt) { return 0; }`. Guess some more details are needed about the context and project settings in your case, – dxiv Jan 06 '21 at 17:14
  • @dxiv I have added a little more at the bottom of my question - don't know if it helps? Could I have a link to your test cosole app to see if I spot anything different? – Andrew Truckle Jan 06 '21 at 18:21
  • 1
    @AndrewTruckle First difference is that all code in my test app is in one and the same file. Your header shows the declarations of the template functions, but it doesn't show where the implementations are defined. Usually, you would have them inlined in the same header, otherwise you need to explicitly instantiate the ones being used somewhere, see the link in the first comment. – dxiv Jan 06 '21 at 18:39
  • @dxiv That linked Q & A has gone over my head I am sorry. My functions are in the CPP file of the class and they too have the same – Andrew Truckle Jan 06 '21 at 18:43
  • @dxiv the linked article referred to another which made a sentence that is now making sense to me "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." So I am moving my code into the header and trying again. – Andrew Truckle Jan 06 '21 at 18:52
  • 1
    @AndrewTruckle The top answers are perfectly up to date. As noted, you don't *have* to inline the implementation into the header, but if you don't then you *must* explicitly instantiate the templates being used, for example add a line `template void ConvertSAFEARRAY>(SAFEARRAY* psaItems, std::map& rItems);` at the end of the `.cpp` where your implementation is defined. – dxiv Jan 06 '21 at 20:54
  • 1
    @dxiv I getcha. What I will do is mark mine as a duplicate anyway since that one led to the answer. And I do thank you for your help. I was struggling with this one. I'll stick with the code inline now. Because I assume I would have had to add more of the `template` lines of code, one for every combination I expected to call. I'll leave it now. :) – Andrew Truckle Jan 06 '21 at 21:45

1 Answers1

0

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.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164