【问题标题】:Why is the return type of C++ function template instantiations included in the mangled function name?为什么 C++ 函数模板实例化的返回类型包含在损坏的函数名称中?
【发布时间】:2015-07-15 11:05:46
【问题描述】:

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 需要ReturnTypebar 不需要?

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

【问题讨论】:

    标签: c++ name-mangling


    【解决方案1】:

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

    1.3.17 签名 &lt;function&gt; 名称、参数类型列表 (8.3.5) 和封闭命名空间(如果有)
    [ 注意: 签名用作 名称修改和链接的基础。尾注 ]


    1.3.18 签名 &lt;function template&gt; 名称,参数类型列表(8.3.5),封闭命名空间(如果有),返回 类型和模板参数列表

    假设我们可以有两个完全不同的函数模板重载,它们的返回类型不同,如果这样写的话:

    template <int>
    char foo();
    
    template <int>
    int foo();
    

    如果名称修改不考虑返回类型,那么链接这些模板将很困难,因为foo&lt;0&gt; 没有唯一地命名一个专业化。不过,可以使用重载解析(不带参数)来解决一种专门化问题:

    int (*funptr)() = foo<0>;   
    

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

    【讨论】:

    • 非常好的例子。我也在想 - 如果返回类型不是签名的一部分,是否会违反 ODR?例如。如果您在 TU 0 中有第一个模板,在 TU 1 中有第二个模板。
    • @dyp 是的,IIRC 声明必须包含相同的标记序列(或等效的标记序列,对于某些等效定义),如果它们属于同一实体。
    • 我想我们可能会互相误解——我的意思在你的最后一段中或多或少得到了回答:名称用于链接,它必须包含返回类型。否则,链接器可能会将 TU 0 中声明(未定义)中的第一个模板与 TU 1 中定义和实例化的第二个模板链接。
    • 您的示例显示了两个模板函数互斥的情况:对于任何Tcopy&lt;T&gt; 将恰好引用其中一个模板函数。为此,您不需要损坏名称中的返回类型。但是我在我的答案中发布了一个(愚蠢的)示例,其中两个有效的模板函数实例具有相同的模板参数和参数类型。你能想出一个可能有用的现实例子吗?我想不出任何问题。
    • @hvd 啊,我明白你的意思了。 IE。模板参数已经唯一地命名了一个专业化。我再举一个例子。
    【解决方案2】:

    与常规函数不同,模板函数可能仅通过返回类型重载。

    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&lt;void&gt;(),但它们不能共享相同的错位名称,否则main 的生成程序集将无法指定它指的是哪个实例化。

    通常,如果您有一个重载的模板函数,则只有一个定义将用于特定的模板参数,所以这并不常见,但在 Columbo 的回答中,dyp 提出了如何实现这一点的基本想法可能实际上很有用。在Can addressof() be implemented as constexpr function?,我想出了

    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&lt;X&gt;,这实际上是违反 ODR 的,有些 X 不完整,有些 X 完整并且具有重载的 &amp; 运算符。这可以通过使用常规重载函数直接执行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&lt;X&gt; 所指的函数与X 完整时不同。

    【讨论】:

    • 但是随着您的更改,带有addressof 的模板ID 始终唯一地引用一个特化,因此这不需要对返回类型进行修改。
    • @Columbo addressof&lt;X&gt; 可能引用一个翻译单元中的第一个定义,但引用另一个翻译单元中的第二个定义,对于相同的X。当这些翻译单元可以链接到一个程序中时,它们的错位名称需要不同。
    • @Columbo 我不明白怎么做,你能详细说明一下吗? 14.6.4.2 是关于如果在其他翻译单元中有addressof 的其他定义,但在其他翻译单元中没有addressof 的其他定义会发生什么。
    • 所以你的模板基本上可以测试一个类类型是否完整。然而,IIRC 已经就这样的模板在技术上是否可行(跨多个 TU)进行了很多讨论,并且一致认为它的应用格式不正确。
    • 删除的评论指的是一个模板您在其中使用了addressof。但是,我不确定它是否适用,事后看来,它没有(想法是选择第二个 TU 中的 addressof 定义,因为它不会 SFINAE 输出,因为 X 是在那个 TU 中定义的) .不过,这看起来仍然很不可思议。
    猜你喜欢
    • 2017-04-09
    • 2019-05-20
    • 1970-01-01
    • 2011-03-04
    • 1970-01-01
    • 2018-04-06
    • 1970-01-01
    • 1970-01-01
    • 2018-06-02
    相关资源
    最近更新 更多