I have a project where I use function templates to allow users to swap in function specializations at compile time, so there is a default version provided in a library of common code and an optional user-provided specialization for a specific subproject.
I would like the common code to be able to tell whether a user actually provided a specialization, but most of the techniques I've found for this rely on deleting the primary template along these lines:
#include <type_traits>
using FunctionPtr = void (*)();
// Primary template
template<class T>
void demo() = delete;
// The common code default implementation
template<>
void demo<int>() { };
// Detect if a specialization exists
template<class T>
concept has_demo = requires { demo<T>(); };
static_assert(has_demo<int>);
static_assert(!has_demo<char>); // False - no user-provided specialization.
I eventually need to store the actual resolved function pointer in a Vulkan dispatch table, so I'm trying to write a resolution function which returns the specialization if it exists and not otherwise:
constexpr FunctionPtr getPointer(void)
{
if constexpr(has_demo<char>)
{
return demo<char>;
}
return demo<int>;
}
... which is subsequently complaining that demo<char>
is hitting the deleted template (which is true, it doesn't exist because the user didn't provide one).
I know I can support this by not deleting the default version and providing a parallel trait that can be tested for the has_..<>()
check, but that requires users to add the "true" trait for every function specialization that they provide, which is a worse user experience in terms of users having to write more code. Is there a way to avoid that?
if constexpr
doesn't work that way outside of a template.
You can put it in a template to fix that:
constexpr FunctionPtr getPointer(void)
{
return []<typename T>{
if constexpr(has_demo<T>)
{
return demo<T>;
}
return demo<int>;
}.operator()<char>();
}
However, this is an ODR nightmare. If you write getPointer
before the specialisation of demo<char>
is declared, this program will be ill-formed NDR (and probably return demo<int>
in practice). This is the same issue with having a parallel has_...<>()
function.