整数类型的模板特化是如何工作的?

7

我有一个模板函数,它只有一个参数<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. 如果longlong 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. 我观察到的行为是通用的还是机器特定的?我如何确保此代码可移植性?

1
诸如 int64_tssize_t 之类的类型只是内置类型 longunsigned long 的别名。与其他一些语言不同,C++ 不提供任何声明“强”类型别名的方法,因此针对相同基础类型的不同别名的模板特化将始终导致错误。 - user7860670
2
#1 是你的错误假设。 “Same size”并不意味着“Same type”。就像char对于signed charunsigned char来说是一个独立的类型,尽管它的行为与其中一个完全相同。 - StoryTeller - Unslander Monica
1
'long'和'long long'在您的编译器中具有相同的大小,但它们不是相同的类型 - Pete Becker
1
@Pete Becker,根据平台/编译器的不同,大小可能并不总是相同。 - Jesper Juhl
2个回答

5

1) 如果longlong long是相同的类型,为什么编译器允许两种特化同时存在?

因为longlong long可以被实现为相同的底层类型,但从语言的角度来看,它们是不同的基本类型。

2) 为什么添加一个int64_t的特化会产生错误?

因为std::int64_t不是算术基本类型,而是另一种类型的别名(通过typedefusing定义)

3) 为什么foo<int64_t>解析为foo<long long>而不是foo<long>

因为在您的平台上,std::int64_t被定义为long long的别名,而不是long(或别名为别名...);因此,在您的平台上,std::int64_tlong 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_tstd::size_t不是基本算术类型,但它们都是其他类型的别名(我想是unsigned longunsigned long long)在您的平台上,它们是不同类型的别名

6) 我在这里观察到的行为是通用的还是机器特定的?我怎样才能确保这段代码是可移植的?

除了点(1)(因为longlong long之间的差异是语言的一部分,因此一直是真实的),其他情况都严重依赖于平台。

但是可以使用std::is_same和其他类型特征来管理它。


3
在C++中,尽管两种类型可能相同,但它们仍然是不同的。例如,char 与 unsigned char 或 signed char 相同,但仍然是不同的类型。在您的情况下,long 和 long long 是相同但不同的类型。这类似于 struct A{}; 和 struct B{}; 是相同但不同的类型。
另一个需要理解的问题是,typedef 和 using 不会创建新类型。
1. 如果 long 和 long long 是相同类型,为什么编译器允许两个特化共存?
即使它们具有相同的大小,类型 long 和 long long 仍然是不同的类型。
2. 为什么添加 int64_t 的特化会导致错误?
固定宽度整数类型是其他内置类型的 typedef。在您的情况下,int64_t 是 long int 或 long long int 的 typedef。您已经为它是别名的任何类型提供了专门化。与前面的情况不同,int64_t 不是命名一个不同的类型。
3. 为什么 foo 解析为 foo 而不是 foo?foo 解析为 foo 而不是 foo?
它可能解析为其中之一。这取决于源代码编译的平台。
4. 为什么 foo 不使用 specialization foo?
同样,uint64_t 和 size_t 别名的类型取决于平台。在这种情况下,它们似乎只是别名不同的类型。
5. 我在这里观察到的行为是否通用或机器特定?我如何确保此代码是可移植的?
大部分你观察到的行为是与平台相关的。尽管可移植性并不意味着在所有平台上行为都相同,只是在所有平台上能做正确的事情。如果你的意图是显示 int 的大小,则在具有不同大小的 int 的平台上行为不同是正常的。最终,这里的可移植性错误在于假设相同类型是相同的类型。
与其假设你使用的类型,你可以使用 std::numeric_limits<type_traits> 头文件,如果你的代码依赖于这些类型的特定细节。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接