I've accepted @davidhigh's answer because I think it's the most appropriate solution for my question as asked, however, in my actual code I've used a different solution, and just in case it helps others, I'll describe it here.
My solution is based on a comment made by @immibis which unfortunately has since been deleted. It was something like "Can't this be done easily using the preprocessor?" I realized that the C *_MAX
macros from climits
can in fact be used, and the solution is very simple. Thanks to @immibis!
I applied a preprocessor guard to all types that can cause a conflict, for both signed and unsigned variants. This consisted of size_t
, uintmax_t
, ssize_t
, ptrdiff_t
, and intmax_t
.
Also, as @tbleher pointed out in his comment, sometimes same-size nominal types can be different true types, the example in question being unsigned long
and unsigned long long
. In fact, on my current system sizeof(unsigned long) == sizeof(unsigned long long) == 8
, and ditto for the signed variants. Although they are the same size, they are considered different true types and will not conflict.
My approach was to first define a function for each of the guaranteed-distinct types, then define a conceptual ordering for the "conflictable" types, and then progressively instantiate a definition for each conflictable type whose size is both (1) greater than the size of [unsigned] long long
and (2) not equal to the size of any conflictable type that sits earlier in the ordering.
Here's 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
Output on my system:
1
2
3
4
5
4
4
8
9
10
11
12
11
11
11
So as it turned out, on my system, all conflictable types do in fact conflict with the true types unsigned long
and long
.
A couple of limitations of this solution are that it can only work for types that have corresponding *_MAX
macros, and it doesn't work for floating-point types, since the preprocessor does not support floating-point arithmetic and comparisons.
sizeof(size_t) == sizeof(unsigned long)
? – Northwestersizeof(size_t) == sizeof(unsigned long)
, it's not guaranteed that they are the same type.size_t
could also be a typedef tounsigned long long
. – Tangentialsizeof()
values are not available to the preprocessor, but max value (and min value) macros likeULONG_MAX
andSIZE_MAX
should be available, so perhaps those would work... @immibis might have found a solution here. – Cytogenesis#if
working with sizes of built-in types, but you're right in thatsize_t
might not be a built-in type but a typedef. – Metzler