为什么返回类型的C++函数模板实例包含在受损函数名中?

Eva*_*nED 13 c++ name-mangling

Itanium ABI 指定,有一些无趣的异常,返回类型包含在模板实例的错位名称中,但不包含在非模板中.

为什么是这样?在什么情况下,您可以有两个函数模板实例,其中链接器需要区分它们,因为它不表示单定义规则违规或类似?

作为我的意思的一个例子:

class ReturnType {};
class ParamType {};

template <typename T>
ReturnType foo(T p)  {
    return ReturnType();
};
template ReturnType foo<ParamType>(ParamType);

ReturnType bar(ParamType p) {
    return ReturnType();
}
Run Code Online (Sandbox Code Playgroud)

然后生成的对象文件具有重整:

ReturnType foo<ParamType>(ParamType)
   => _Z3fooI9ParamTypeE10ReturnTypeT_
                        ^^^^^^^^^^^^

ReturnType bar(ParamType)
   => _Z3bar9ParamType
Run Code Online (Sandbox Code Playgroud)

为什么foo需要ReturnType损坏但bar不是?

(我假设有一个原因,这不仅仅是一个随意的选择.)

Col*_*mbo 15

也许是因为,与普通函数相反,函数模板签名包含返回类型?§1.3:

1.3.17签名 <函数>名称,参数类型列表(8.3.5)和封闭命名空间(如果有)
[ 注意:签名用作名称修改和链接的基础.- 结束说明 ]


1.3.18签名 <函数模板>名称,参数类型列表(8.3.5),封闭命名空间(如果有),返回类型和模板参数列表

考虑到我们可以有两个完全不同的函数模板重载,只有它们的返回类型不同,如果这样编写:

template <int>
char foo();

template <int>
int foo();
Run Code Online (Sandbox Code Playgroud)

如果名称重整不会考虑返回类型,那么链接这些模板将会很困难,因为foo<0>不能唯一地命名一个专门化.仍然可以使用重载解析(无参数)来解决一个特化问题:

int (*funptr)() = foo<0>;   
Run Code Online (Sandbox Code Playgroud)

另一方面,普通函数不需要包含返回类型,因为它们的返回类型不能重载 - 即它们的签名不包括返回类型.


小智 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();
}
Run Code Online (Sandbox Code Playgroud)

这里,假设一个非优化编译器,生成的程序集将包含两个函数f<void>(),但它们不能共享相同的受损名称,或者生成的程序集main无法指定它引用的实例化.

通常情况下,如果你有一个重载的模板函数,只有一个定义将用于特定的模板参数,所以这是不常见的,但在对Columbo的回答的评论中,dyp提出了这可能实际上是如何实现的基本思路有用.在Can addressof()中实现constexpr函数?我想出来了

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);
}
Run Code Online (Sandbox Code Playgroud)

但是,如果addressof<X>在多个翻译单元中使用相同的实例化,某些地方X是不完整的,有些地方X是完整的并且有一个重载的&运算符,这实际上是一个ODR违规.这可以通过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;
}
Run Code Online (Sandbox Code Playgroud)

(has_overloaded_addressof_operator出于同样的原因,也需要内联.)

这样就避免了问题:当X不完整时,则addressof<X>指的是与X完成时不同的功能.