0
votes

I have a class like this

/**
  Some third party libraries with C interfaces have their own set of functions to allocate and free 
  heap memory buffers aligned to the specification of the library. This class helps to manage such
  buffers in a convenient RAII style

  @tparam Type The type of data to hold in the buffer
  @tparam SizeType The type used for sizes (in most cases either int or size_t)
  @tparam allocFunction A function to call that takes the number of bytes to allocate as argument and returns a pointer
                        of Type* to the allocated memory
  @tparam freeFunction A function to call that takes a void* pointer and frees the previously allocated memory
 */
template <typename Type, typename SizeType, Type* (*allocFunction)(SizeType), void (*freeFunction)(void*)>
class GenericScopedBuffer

This worked fine until I now tried to update one of our older products that is still built as a 32 Windows version. We use some library that has their allocation/deallocation functions defined as __stdcall types and passing them to this class template like e.g.

GenericScopedBuffer<char, int, stdcallAllocFunction, stdcallFreeFunction>

leads to a compiler error like C2440: 'specialization': cannot convert from 'void (__stdcall *)(void *)' to 'void (__cdecl *)(void *)'

The obvious reason is that the x86 compiler assumes a function pointer declared like that as a pointer to a __cdecl function. Now as this class template should in fact work with a function pointer any kind of function I wonder if there is a way to deduct the calling convention type from the function passed in. However, __stdcall or __cdecl are not really types but more like hints to the compiler, right?

So what would be a good way to enable this in a cross-platform and generic way?

1
Assuming that your default convention is cdecl, when you write the function template it automatically add the __cdecl. __cdecl and __stdcall are not just hints, but the way the function is built and called and that's why you can't cast between cdecl function and stdcall function without getting some stack errors. Instead, you can use a simple typename and check it with std::is_function. - Roy Avidan
Yes I know about the fundamental difference between __cdecl and __stdcall. However what do you mean by "you can use a simple typename and check it with std::is_function". Would you have some short demo code for me to elaborate on this? - PluginPenguin

1 Answers

1
votes

void (__stdcall *)(void *) and void (__cdecl *)(void *) are two different types as far as MSVC is concerned. These qualifiers are a kind of pre-standard way to specify language linkage. Recall that an extern "C" function is different from an extern "C++" function which is different from extern "FORTRAN" function, even though some compilers let you get away with mixing up those.

There is a couple of ways to deal with this.

You can specialise your template for __cdecl functions.

You can tell the compiler you don't care what the type is:

template <auto allocFunction, auto freeFunction> class GenericScopedBuffer 

If you don't have access to the modern version of C++, you can parameterize by function type:

template <typename AllocFuncT, AllocFuncT allocFunction, 
          typename FreeFuncT, FreeFuncT freeFunction> class GenericScopedBuffer

but then of course you have to specify the type by hand. You can use decltype for that, possibly combined with a silly macro like

#define TV(x) decltype(x), (x)