7

I have a template function with a single parameter <T>, and I would like to make specializations of this function for different integral types. This seemed obvious at first, however after few attempts I found that I do not really understand how the specialization really works here, and how to achieve some degree of portability...

Here's the test program:

// clang test.cc -std=c++11 -lc++
#include <iostream>
#include <typeinfo>

template <typename T> void foo()  { std::cout << "  foo<T> with T=" << typeid(T).name() << '\n'; }
template <> void foo<int>()       { std::cout << "  foo<int>\n"; }
template <> void foo<long>()      { std::cout << "  foo<long>\n"; }
template <> void foo<long long>() { std::cout << "  foo<long long>\n"; }
template <> void foo<size_t>()    { std::cout << "  foo<size_t>\n"; }
// template <> void foo<int64_t>()  { std::cout << "  foo<int64_t>\n"; } // error


int main () {
  std::cout << "sizeof(int)=" << sizeof(int) << ", ";
  std::cout << "sizeof(long)=" << sizeof(long) << ", ";
  std::cout << "sizeof(long long)=" << sizeof(long long) << ", ";
  std::cout << "sizeof(size_t)=" << sizeof(size_t) << "\n";
  foo<int>();
  foo<long>();
  foo<long long>();
  foo<size_t>();
  foo<ssize_t>();
  foo<int8_t>();
  foo<int16_t>();
  foo<int32_t>();
  foo<int64_t>();
  foo<uint32_t>();
  foo<uint64_t>();
  return 0;
}

and on my machine it produces

sizeof(int)=4, sizeof(long)=8, sizeof(long long)=8, sizeof(size_t)=8
  foo<int>
  foo<long>
  foo<long long>
  foo<size_t>
  foo<long>
  foo<T> with T=a
  foo<T> with T=s
  foo<int>
  foo<long long>
  foo<T> with T=j
  foo<T> with T=y

So here's what I don't understand:

  1. If long and long long is the same type, why does the compiler allow both specializations to coexist?
  2. Why adding a specialization for int64_t produces an error?
  3. Why foo<int64_t> resolves as foo<long long> and not foo<long>?
  4. Why foo<ssize_t> resolves as foo<long> and not foo<long long>?
  5. Why foo<uint64_t> does not use specialization foo<size_t>?
  6. Is the behavior that I observe here universal, or machine-specific? How can I be sure that this code is portable?
max66
  • 65,235
  • 10
  • 71
  • 111
Pasha
  • 6,298
  • 2
  • 22
  • 34
  • 1
    Types such as `int64_t` and `ssize_t` are just aliases for built-in types such as `long` or `unsigned long`. Unlike some other languages, C++ does not offer any means to declare "strong" type aliases so template specializations for different aliases for the same underlying type will always result in error. – user7860670 Feb 15 '18 at 19:30
  • 2
    #1 is a false assumption on your part. "Same size" doesn't mean "Same type". Just like a `char` is a distinct type to both `signed char` and `unsigned char`, even though it behaves identically to one of them. – StoryTeller - Unslander Monica Feb 15 '18 at 19:32
  • 1
    'long' and 'long long' are the same **size** with your compiler, but they are not the same **type**. – Pete Becker Feb 15 '18 at 19:55
  • 1
    @Pete Becker and, depending on platform/compiler, may not *always* be the same size. – Jesper Juhl Feb 15 '18 at 20:07

2 Answers2

5

1) If long and long long is the same type, why does the compiler allow both specializations to coexist?

Because long and long long can be implemented over the same low level type but, from the point of view of the language, are different fundamental types.

2) Why adding a specialization for int64_t produces an error?

Because std::int64_t isn't an arithmetic fundamental type but is an alias (defined through typedef or using) of another type

3) Why foo<int64_t> resolves as foo<long long> and not foo<long>?

Because, in your platform, std::int64_t is defined as an alias for long long, not for long (or an alias for an alias...); so, in your platform, std::int64_t is long long; in different platform, you can have different results

4) Why foo<ssize_t> resolves as foo<long> and not foo<long long>?

Same as std::int64_t: the type ssize_t (not a standard type) is an alias (in your platform) for long, not for long long

5) Why foo<uint64_t> does not use specialization foo<size_t>?

Because std::uint64_t and std::size_t aren't fundamental arithmetic types but both are alias for other types (unsigned long and unsigned long long, I suppose) and, in your platform, they are alias of different types

6) Is the behavior that I observe here universal, or machine-specific? How can I be sure that this code is portable?

With exception for point (1) (that is ever true because the difference between long and long long is part of the language), is heavily platform dependent.

But it's possible to manage it using, by example, std::is_same and other type traits.

max66
  • 65,235
  • 10
  • 71
  • 111
3

In c++, two types may be distinct despite being identical. For example, char is identical to either unsigned char or signed char but is still a distinct type. In your case, long and long long are identical but distinct. This is similar to how struct A{}; and struct B{}; are identical but distinct types.

The other things to understand is that typedef and using to not create a new type.

  1. If long and long long is the same type, why does the compiler allow both specializations to coexist?

The types long and long long are distinct types, even if they have the same size.

  1. Why adding a specialization for int64_t produces an error?

The fixed width integer types are typedefs for other built-in types. In your case, int64_t is a typedef to long int or long long int. You've already provided a specialize for whichever type it's an alias for. Unlike the previous case, int64_t does not name a distinct type.

  1. Why foo<int64_t> resolves as foo<long long> and not foo<long>?
  2. Why foo<ssize_t> resolves as foo<long> and not foo<long long>?

It might resolve to one or the other. It depends on the platform the source code is compiling for.

  1. Why foo<uint64_t> does not use specialization foo<size_t>?

Again, what types uint64_t and size_t alias depends on the platform. It seems that in this case they simply alias different types.

  1. Is the behavior that I observe here universal, or machine-specific? How can I be sure that this code is portable?

Most of the behavior you observed is platform dependent. Though portability doesn't imply that the behavior will be identical on all platforms, only that it will do the right thing on all platforms. If your intention is to display the size of int, then it's normal that the behavior be different on platforms with different sized int. Ultimately, the portability error here is to assume identical types are the same type.

Rather than assume anything about the types you use, you can use std::numeric_limits and on the <type_traits> header if you code depends on the specific details of those types.

François Andrieux
  • 28,148
  • 6
  • 56
  • 87