Как работает специализация шаблонов с целочисленными типами?
у меня есть функция шаблона с одним параметром <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
вот что я не понимаю:
- если
long
иlong long
является одним и тем же типом, почему компилятор позволяет обе специализации сосуществовать? - зачем добавлять специализацию для
int64_t
выдает ошибку? - почему
foo<int64_t>
решает какfoo<long long>
, а неfoo<long>
? - почему
foo<ssize_t>
решает какfoo<long>
, а неfoo<long long>
? - почему
foo<uint64_t>
не использует специализациюfoo<size_t>
? - - это поведение, которое я наблюдаю здесь универсальный или машинный? Как я могу быть уверен, что этот код переносим?
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 не создать новый тип.
- если
long
иlong long
является одним и тем же типом, почему компилятор позволяет обе специализации сосуществовать?
типы long
и long long
- разные типы, даже если они имеют одинаковый размер.
- зачем добавлять специализацию для
int64_t
выдает ошибку?
целочисленные типы фиксированной ширины:typedef
S для других встроенных типов. В твоем случае, int64_t
является typedef для long int
или long long int
. Ты уже предоставлена специализация для любого типа, для которого это псевдоним. В отличие от предыдущего случая, int64_t
не называет отдельный тип.
- почему
foo<int64_t>
решает какfoo<long long>
, а неfoo<long>
?- почему
foo<ssize_t>
решает какfoo<long>
, а неfoo<long long>
?
это может решить один или другой. Это зависит от платформы, для которой компилируется исходный код.
- почему
foo<uint64_t>
не использует специализациюfoo<size_t>
?
опять же, какие типы uint64_t
и size_t
псевдоним зависит от платформы. Похоже, что в этом случае они просто псевдонимы разных типов.
- является ли поведение, которое я наблюдаю здесь, универсальным или машинным? Как я могу быть уверен, что этот код переносим?
большая часть поведения, которое вы наблюдали, зависит от платформы. Хотя переносимость это не означает, что поведение будет идентичным на всех платформах, только то, что оно будет делать правильные вещи на всех платформах. Если ваше намерение состоит в том, чтобы отобразить размер int
, то это нормально, что поведение будет отличаться на платформах с разным размером int
. В конечном счете, ошибка переносимости здесь заключается в предположении, что идентичные типы являются одним и тем же типом.
вместо того, чтобы предполагать что-либо о типах, которые вы используете, вы можете использовать std::numeric_limits
и на <type_traits>
заголовок, если код зависит от конкретных деталей этих типов.