Как работает специализация шаблонов с целочисленными типами?

у меня есть функция шаблона с одним параметром <T>, и я хотел бы сделать специализации этой функции для различных целочисленных типов. Сначала это казалось очевидным, однако после нескольких попыток я обнаружил, что не совсем понимаю, как здесь действительно работает специализация и как добиться некоторой степени переносимости...

вот тестовая программа:

// 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;
}

и на моей машине она производит

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

вот что я не понимаю:

  1. если long и long long является одним и тем же типом, почему компилятор позволяет обе специализации сосуществовать?
  2. зачем добавлять специализацию для int64_t выдает ошибку?
  3. почему foo<int64_t> решает как foo<long long>, а не foo<long>?
  4. почему foo<ssize_t> решает как foo<long>, а не foo<long long>?
  5. почему foo<uint64_t> не использует специализацию foo<size_t>?
  6. - это поведение, которое я наблюдаю здесь универсальный или машинный? Как я могу быть уверен, что этот код переносим?

2 ответов


1) Если long и long long является одним и тем же типом, почему компилятор позволяет обе специализации сосуществовать?

, потому что long и long long может быть реализован на одном и том же типе низкого уровня, но, с точки зрения языка, являются различными фундаментальными типами.

2) зачем добавлять специализацию для int64_t выдает ошибку?

, потому что std::int64_t не арифметическая основных типа, но псевдоним (определяется через typedef или using) другого типа

3) Почему foo<int64_t> решает как foo<long long>, а не foo<long>?

потому что, в свою платформу, std::int64_t определяется как псевдоним для long long, не long (или псевдоним псевдоним...); Итак, в вашей платформе,std::int64_t is long long; в другой платформе вы можете иметь разные результаты

4) Почему foo<ssize_t> решает как foo<long> и не foo<long long>?

же std::int64_t тип ssize_t (не стандартный тип) - это псевдоним (в вашей платформе) для long, не long long

5) Почему foo<uint64_t> не использует специализацию foo<size_t>?

, потому что std::uint64_t и std::size_t не являются фундаментальными арифметическими типами, но оба являются псевдонимами для других типов (unsigned long и unsigned long long, я полагаю), и в вашей платформе они являются псевдонимами разных типов

6) является ли поведение, которое я наблюдаю здесь, универсальным или машинным? Как я могу быть уверен, что этот код переносим?

за исключением точки (1) (это всегда верно, потому что разница между long и long long является частью языка), сильно зависит от платформы.

но можно управлять им, используя, например,std::is_same и другие черты типа.


на c++, два типа могут быть разными, несмотря на то, что они идентичны. Например, char идентично либо unsigned char или signed char но есть еще особый тип. В твоем случае, long и long long идентичны, но различны. Это похоже на how struct A{}; и struct B{}; являются идентичными, но различными типами.

другие вещи, чтобы понять, что typedef и using to не создать новый тип.

  1. если long и long long является одним и тем же типом, почему компилятор позволяет обе специализации сосуществовать?

типы long и long long - разные типы, даже если они имеют одинаковый размер.

  1. зачем добавлять специализацию для int64_t выдает ошибку?

целочисленные типы фиксированной ширины:typedefS для других встроенных типов. В твоем случае, int64_t является typedef для long int или long long int. Ты уже предоставлена специализация для любого типа, для которого это псевдоним. В отличие от предыдущего случая, int64_t не называет отдельный тип.

  1. почему foo<int64_t> решает как foo<long long>, а не foo<long>?
  2. почему foo<ssize_t> решает как foo<long>, а не foo<long long>?

это может решить один или другой. Это зависит от платформы, для которой компилируется исходный код.

  1. почему foo<uint64_t> не использует специализацию foo<size_t>?

опять же, какие типы uint64_t и size_t псевдоним зависит от платформы. Похоже, что в этом случае они просто псевдонимы разных типов.

  1. является ли поведение, которое я наблюдаю здесь, универсальным или машинным? Как я могу быть уверен, что этот код переносим?

большая часть поведения, которое вы наблюдали, зависит от платформы. Хотя переносимость это не означает, что поведение будет идентичным на всех платформах, только то, что оно будет делать правильные вещи на всех платформах. Если ваше намерение состоит в том, чтобы отобразить размер int, то это нормально, что поведение будет отличаться на платформах с разным размером int. В конечном счете, ошибка переносимости здесь заключается в предположении, что идентичные типы являются одним и тем же типом.

вместо того, чтобы предполагать что-либо о типах, которые вы используете, вы можете использовать std::numeric_limits и на <type_traits> заголовок, если код зависит от конкретных деталей этих типов.