为什么 declval 出现在损坏的符号名称中?

Why is declval present in a mangled symbol name?

我在玩 GCC (4.9.2) abi::__cxa_demangle 时遇到了无法分解特定符号名称的情况。

那个符号是:

_ZNK12DebugWrapperIR5TestClsE5getIdIIEEEDTcldtcl7declvalIS1_EEL_ZNKS0_5getIdEvEspfp_EEDpOT_ 

我很惊讶地看到 "declval" 在那里。

该特定函数是使用此宏定义的:

template <typename WrappedType>
class DebugWrapper
{
private:
WrappedType VALUE;
std::string NAME;
mutable std::string CALLER;

public:
...
#define WRAP_CONST_FUNCTION(name, enterLogging, exitLogging)                                    \
        template <typename ...Args>                                                             \
        auto name(Args&&... args) const -> decltype(std::declval<WrappedType>().name(args...))  \
        {                                                                                       \
                                                                                            \
            struct dummy                                                                        \
            {                                                                                   \
                const char* const FUNCTION = nullptr;                                           \
                const std::string& CALLER;                                                      \
                const WrappedType& VALUE;                                                       \
                                                                                                \
                dummy(  const char* const input,                                                \
                        const std::string& caller,                                              \
                        const WrappedType& value):                                              \
                    FUNCTION(input), CALLER(caller), VALUE(value)                               \
                {                                                                               \
                    enterLogging;                                                               \
                }                                                                               \
                                                                                                \
                ~dummy()                                                                        \
                {                                                                               \
                    exitLogging;                                                                \
                }                                                                               \
            }dummy(#name, CALLER, VALUE);                                                       \
                                                                                                \
            return VALUE.name(args...);                                                         \
        }
        WRAP_CONST_FUNCTION(getId, <whatever>, <whatever>)
...
};

我还通过 itanium c++ abi spec 快速搜索了 declval,但没有结果。

它为什么在那里?为什么不能 abi::__cxa_demangle demangle 呢?

模板函数需要让它们的 return 类型出现在错位名称中,因为模板函数可以单独在 return 类型上重载。一个更简单的例子是

template <typename T> void f() { }
template <typename T> auto g() -> decltype(f<T>()) { }
inline void h() { }
int main() { g<int>(); h(); }

编译并检查输出,我看到:

$ g++ -c h.cc -std=c++11 && nm h.o | c++filt
0000000000000000 T main
0000000000000000 W decltype ((f)()) g()
0000000000000000 W h()

您可以看到 g 的 return 类型,但 h 看不到。 h 不是模板函数这一事实已经意味着在同一个命名空间中不能有另一个具有相同参数的函数 h

这也是为什么 getId 在您的错误名称中出现两次的原因。其中之一是名称本身,另一个来自它在 return 类型中的出现。

declval 在这里并不特殊,这就是为什么它没有在 C++ ABI 中被调用的原因。任何函数、库或用户都以同样的方式处理。

至于为什么拆不开,不好说。在您的问题中生成损坏名称的真实代码示例在这里会有所帮助,但是 5TestCls 看起来不对,因为名称前面的数字表示长度,我确实得到 TestCls 的印象应该是全名。如果那确实是名字,那么分解就会失败,因为分解后的名字是无效的。

基于完整的例子,你在评论中发布的那个,我可以想出一个简化的程序,似乎是成员访问运算符没有被分解器正确处理:

extern struct A { void f(); static void g(); } a;
template <typename...T> auto f(T...t) -> decltype(a.f(t...));
template <typename...T> auto g(T...t) -> decltype(A::g(t...));
int main() { f(); g(); }
$ g++ -std=c++11 -pedantic -c h.cc && nm h.o && nm -C h.o
0000000000000000 T main
                 U _Z1fIJEEDTcldtL_Z1aEL_ZN1A1fEvEspfp_EEDpT_
                 U _Z1gIJEEDTclL_ZN1A1gEvEspfp_EEDpT_
0000000000000000 T main
                 U _Z1fIJEEDTcldtL_Z1aEL_ZN1A1fEvEspfp_EEDpT_
                 U decltype (A::g({parm#1}...)) g<>()

_Z1fIJEEDTcldtL_Z1aEL_ZN1A1fEvEspfp_EEDpT__Z1gIJEEDTclL_ZN1A1gEvEspfp_EEDpT_ 之间的区别在一些额外的间距下更加明显:

_Z1fIJEEDTcldtL_Z1aEL_ZN1A1fEvEspfp_EEDpT_
_Z1gIJEEDTcl        L_ZN1A1gEvEspfp_EEDpT_

唯一的区别是 fgdtL_Z1aE

Itanium C++ ABI 指定 x.y 被破坏为 dt <expression> <unresolved-name>。这里,<expression>L_Z1aE。那部分看起来不错。这表示 a 全局变量的 "mangled"(不是真的)名称。但是 <unresolved-name>_ZN1A1fEvE。那是错误的。这对于 decltype(A::g(t...)) 版本是正确的,其中函数调用运算符的最左边的操作数可以通过 <expr-primary> ::= L <mangled-name> E 产生式表示为一个错位的名称,但对于 decltype(a.f(t...)) 版本则不然。这意味着类似于 A::f(),但是 <unresolved-name> 不应该有命名空间限定符,除非它们实际出现在源代码中,即使那样,也只有一个特殊的前缀(sr)。它也不应该有参数信息。 <unresolved-name> 应该只是 1f.

如果使用更正后的名字,demangler可以处理:

$ c++filt <<< _Z1fIJEEDTcldtL_Z1aE1fspfp_EEDpT_              
decltype ((a.f)({parm#1}...)) f<>()

clang 确实生成了正确的名称,正如在 Coliru 上看到的那样,只需在您的命令调用中将 g++ 更改为 clang++。您可能希望将此作为错误报告给 GCC 开发人员。