Say I have a simple nullary template function templated on a single parameter, with two specializations, one for unsigned long
, and one for size_t
(contents not important):
template<typename T> T f(void);
template<> unsigned long f<unsigned long>(void) { return 1; }
template<> size_t f<size_t>(void) { return 2; }
My understanding is that the exact definition of the type size_t
is platform-dependent, and so it may or may not be equivalent to unsigned long
. On my current platform (Cygwin g++ 5.2.0 on Windows 10 64-bit compiling with -std=gnu++1y
) these two types appear to be equivalent, so the above code fails to compile:
../test.cpp:51:19: error: redefinition of ‘T f() [with T = long unsigned int]’
template<> size_t f<size_t>(void) { return 2; }
^
../test.cpp:50:26: note: ‘T f() [with T = long unsigned int]’ previously declared here
template<> unsigned long f<unsigned long>(void) { return 1; }
^
From my thinking, this problem could be solved by simply disabling the size_t
function definition, since any code that attempted to call f<size_t>()
would automatically resolve to f<unsigned long>()
. But the function should be enabled for platforms that define size_t
to be different from unsigned long
.
I've read a little bit about template metaprogramming and SFINAE, and I've been playing with things like this:
std::enable_if<(sizeof(size_t) > sizeof(unsigned long))>::type
But I'm unsure how to use such a fragment to disable a global function definition, if that's possible at all.
So, is there any way to conditionally disable a global function definition using template metaprogramming? Or, more generally, am I on the right track, or heading down a wrong path?
This works in any case, but it's a bit tedious and doesn't scale that well for a larger number of specializations:
template<typename T
, std::enable_if_t<!std::is_same<T, unsigned long>::value
&& !std::is_same<T, size_t>::value>* = nullptr>
T f() { return 1; }
template<typename T
, std::enable_if_t<std::is_same<T, unsigned long>::value>* = nullptr>
T f() { return 2; }
template<typename T
, std::enable_if_t<std::is_same<T, size_t>::value
&& !std::is_same<T, unsigned long>::value>* = nullptr>
T f() { return 3; }
The idea: don't specialize but overload, and enable the overloads only if the signature is appropriate (while disabling the others at the same time).
Further, in order to make it better maintainable, one should outsource the logical checks into another suitable class.
DEMO:
int main()
{
std::cout<<f<int>()<<std::endl;
std::cout<<f<unsigned long>()<<std::endl;
std::cout<<f<size_t>()<<std::endl;
std::cout<<f<unsigned long long>()<<std::endl;
}
It prints:
1
2
2
1
So it seems "size_t == unsigned long
" on coliru.
From my experience: Not with global functions directly (reading davidhigh's answer while typing: ok, it works, but as he said it doesnt scale well). SFINAE only works, if the "error" appears while resolving template parameters. Since C++ allows function templates to be fully specialized only, there is no "resolving" when the compiler tries to compile a specialisation.
However with classes the compiler allows for partial specializations and you can do something like this, which has the benefit that you need the SFINAE-expression only for the size_t (using a mySize here since i can change it):
#include <iostream>
#include <type_traits>
using namespace std;
typedef unsigned int mySize;
//default
template <class P, class dummy = void>
class T{
public:
static P f(){return 0;}
};
//int
template <>
class T<int,void> {
public:
static int f(){return 1;}
};
//unsigned long
template <>
class T<unsigned long, void> {
public:
static unsigned long f(){return 2;}
};
template <class P>
class T<P, typename enable_if<is_same<P, mySize>::value && !is_same<P, unsigned long>::value, void>::type> {
public:
static P f(){return 3;}
};
int main() {
cout << T<int>::f() << endl;
cout << T<unsigned long>::f() << endl;
cout << T<mySize>::f() << endl;
return 0;
}
Output with typedef unsigned long mySize;
:
1
2
2
Output with any other typedef (well, not int for obvious reasons):
1
2
3
Try it online
Here's an approach that is a bit strange, but fairly easy to work with:
//using MyType = unsigned int;
using MyType = unsigned long;
unsigned long f2(MyType *,int) { return 1; }
size_t f2(size_t *,...) { return 2; }
template <typename T>
auto f() -> decltype(f2(static_cast<T*>(0),0)) {
T* p = 0;
return f2(p,0);
}
int main()
{
std::cout << f<MyType>() << "\n";
std::cout << f<size_t>() << "\n";
}
The idea here is that you can make a distinct function for the size_t case which will not be preferred, but will be used if there is no other option. If size_t
and MyType
are the same, then the MyType
overload will be used, otherwise the size_t
overload will be used.
f
calls f2
and uses a trailing return type with decltype
so that if f2
doesn't exist for a particular type T, then f
won't exist either.
Using this technique, you can easily add overloads for other types as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With