Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does template specialization with integer types work?

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?
like image 823
Pasha Avatar asked Feb 15 '18 19:02

Pasha


1 Answers

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.

like image 65
max66 Avatar answered Sep 28 '22 01:09

max66