Itanium ABI specifies,除了几个无趣的例外,返回类型包含在模板实例的错位名称中,但不包含在非模板中。
这是为什么?在什么情况下,您可以有两个函数模板实例,其中链接器需要区分它们,因为它不表示单定义规则违规或类似情况?
作为我的意思的一个例子:
class ReturnType {};
class ParamType {};
template <typename T>
ReturnType foo(T p) {
return ReturnType();
};
template ReturnType foo<ParamType>(ParamType);
ReturnType bar(ParamType p) {
return ReturnType();
}
然后生成的目标文件包含了错误:
ReturnType foo<ParamType>(ParamType)
=> _Z3fooI9ParamTypeE10ReturnTypeT_
^^^^^^^^^^^^
ReturnType bar(ParamType)
=> _Z3bar9ParamType
为什么foo
需要ReturnType
受损,但bar
没有?
(我认为有一个理由,它不仅仅是一种随意的选择。)
答案 0 :(得分:15)
也许是因为,与普通函数相反,函数模板签名包含返回类型? §1.3:
1.3.17签名
<小时/> 1.3.18签名<
函数>
名称,参数类型列表(8.3.5)和封闭名称空间(如果有)
[注意:签名用作 名称修改和链接的基础。 - 结束记录]
<
函数模板>
名称,参数类型列表(8.3.5),封闭命名空间(如果有),返回 输入,以及模板参数列表
考虑到我们可以有两个完全不同的函数模板重载,只有它们的返回类型不同,如果这样写的话:
template <int>
char foo();
template <int>
int foo();
如果名称修改不考虑返回类型,那么链接这些模板会很困难,因为foo<0>
没有唯一地命名一个专门化。仍然可以使用重载解析(无参数)解决一个特化:
int (*funptr)() = foo<0>;
另一方面,普通函数不需要包含返回类型,因为它们的返回类型不能重载 - 即它们的签名不包括返回类型。
答案 1 :(得分:8)
与常规函数不同,模板函数可能仅由返回类型重载。
template <typename T> int f() { return 1; }
template <typename T> long f() { return 2; }
int main() {
int (&f1) () = f<void>;
long (&f2) () = f<void>;
return f1() == f2();
}
这里,假设一个非优化编译器,生成的程序集将包含两个函数f<void>()
,但它们不能共享相同的错位名称,或者{{1}无法生成程序集指定它引用的实例化。
通常情况下,如果你有一个重载的模板函数,只有一个定义将用于特定的模板参数,所以这是不常见的,但在对Columbo的回答的评论中,dyp提出了如何这个的基本思路实际上可能有用。在Can addressof() be implemented as constexpr function?中,我想出了
main
但如果在多个翻译单元中使用相同的实例template <bool>
struct addressof_impl;
template <>
struct addressof_impl<false> {
template <typename T>
static constexpr T *impl(T &t) {
return &t;
}
};
template <>
struct addressof_impl<true> {
template <typename T>
static /* not constexpr */ T *impl(T &t) {
return reinterpret_cast<T *>(&const_cast<char &>(reinterpret_cast<const volatile char &>(t)));
}
};
template <typename T>
constexpr T *addressof(T &t)
{
return addressof_impl<has_overloaded_addressof_operator<T>::value>::template impl<T>(t);
}
,有些addressof<X>
不完整,有些X
已完成并且过载,则实际上是ODR违规X
运营商。这可以通过使用常规重载函数直接在&
内执行逻辑来重新编写。
addressof
(template <typename T>
std::enable_if_t<has_overloaded_addressof_operator<T>::value, T *>
addressof(T &t)
{
return reinterpret_cast<T *>(&const_cast<char &>(reinterpret_cast<const volatile char &>(t)));
}
template <typename T>
constexpr
std::enable_if_t<!has_overloaded_addressof_operator<T>::value, T *>
addressof(T &t)
{
return &t;
}
也需要内联,出于同样的原因。)
这样就避免了问题:当has_overloaded_addressof_operator
不完整时,X
指的是与addressof<X>
完成时不同的功能。