I accepted @ davidhigh's answer because I think this is the most suitable solution for my question, as was asked, however, I used a different solution in my actual code, and just in case it helps others, I will describe it here.
My decision is based on comments made by @immibis, which unfortunately has since been removed. It was something like, "Could this be done easily using a preprocessor?" I realized that the C *_MAX from climits can actually be used, and the solution is very simple. Thanks @immibis!
I applied preprocessor protection to all types that could cause a conflict, both for signed and unsigned options. This consisted of size_t , uintmax_t , ssize_t , ptrdiff_t and intmax_t .
In addition, as @tbleher noted in his comment, sometimes nominal types of the same size can be different true types, for example, unsigned long and unsigned long long . In fact, in my current system, sizeof(unsigned long) == sizeof(unsigned long long) == 8 , as well as for signed options. Although they are the same size, they are considered different true types and do not conflict.
My approach was to first define a function for each of the guaranteed distinct types, then define a conceptual order for the "conflicting" types, and then gradually create a definition for each conflicting type whose size is (1) greater than the size [unsigned] long long and (2), not equal to the size of any conflicting type that sits earlier in the order.
Here is a demo:
#include <climits> // most integer limit macros, including SSIZE_MAX #include <cstddef> // size_t, ptrdiff_t, [u]intmax_t #include <cstdint> // SIZE_MAX, PTRDIFF_{MIN,MAX}, UINTMAX_MAX, INTMAX_{MIN,MAX} #include <sys/types.h> // ssize_t #include <cstdio> // primary template template<typename T> void f(void); // declarations -- guaranteed not to cause conflicts; dups are allowed template<> void f<unsigned char>(void); template<> void f<unsigned short>(void); template<> void f<unsigned int>(void); template<> void f<unsigned long>(void); template<> void f<unsigned long long>(void); template<> void f<size_t>(void); template<> void f<uintmax_t>(void); template<> void f<char>(void); template<> void f<short>(void); template<> void f<int>(void); template<> void f<long>(void); template<> void f<long long>(void); template<> void f<ssize_t>(void); template<> void f<ptrdiff_t>(void); template<> void f<intmax_t>(void); int main(void) { f<unsigned char>(); f<unsigned short>(); f<unsigned int>(); f<unsigned long>(); f<unsigned long long>(); f<size_t>(); f<uintmax_t>(); f<char>(); f<short>(); f<int>(); f<long>(); f<long long>(); f<ssize_t>(); f<ptrdiff_t>(); f<intmax_t>(); return 0; } // end main() // definitions -- must use preprocessor guard on conflictable types template<> void f<unsigned char>(void) { std::printf("%d\n",1); } template<> void f<unsigned short>(void) { std::printf("%d\n",2); } template<> void f<unsigned int>(void) { std::printf("%d\n",3); } template<> void f<unsigned long>(void) { std::printf("%d\n",4); } template<> void f<unsigned long long>(void) { std::printf("%d\n",5); } #if SIZE_MAX > ULLONG_MAX template<> void f<size_t>(void) { std::printf("%d\n",6); } #endif #if UINTMAX_MAX > ULLONG_MAX && UINTMAX_MAX != SIZE_MAX template<> void f<uintmax_t>(void) { std::printf("%d\n",7); } #endif template<> void f<char>(void) { std::printf("%d\n",8); } template<> void f<short>(void) { std::printf("%d\n",9); } template<> void f<int>(void) { std::printf("%d\n",10); } template<> void f<long>(void) { std::printf("%d\n",11); } template<> void f<long long>(void) { std::printf("%d\n",12); } #if SSIZE_MAX > LLONG_MAX template<> void f<ssize_t>(void) { std::printf("%d\n",13); } #endif #if PTRDIFF_MAX > LLONG_MAX && PTRDIFF_MAX != SSIZE_MAX template<> void f<ptrdiff_t>(void) { std::printf("%d\n",14); } #endif #if INTMAX_MAX > LLONG_MAX && INTMAX_MAX != SSIZE_MAX && INTMAX_MAX != PTRDIFF_MAX template<> void f<intmax_t>(void) { std::printf("%d\n",15); } #endif
The output on my system is:
1 2 3 4 5 4 4 8 9 10 11 12 11 11 11
So, as it turned out, in my system all conflicting types really conflict with the true types of unsigned long and long .
A couple of limitations of this solution is that it can only work for types with the corresponding *_MAX macros, and it does not work for floating point types, because the preprocessor does not support floating point arithmetic and comparison.