Question

Consider the following function:

template <typename A, typename B>
auto Min(A&& a, B&& b)
        -> decltype(a < b ? std::forward<A>(a) : std::forward<B>(b))
{
    return a < b ? std::forward<A>(a) : std::forward<B>(b);
}

The fragment Min(0, 1) causes the template to be instantiated as Min<int, int>. Strangely, the mangled name for Min with g++ and clang for my code is _Z3MinIiiEDTqultfp_fp0_cl7forwardIT_Efp_Ecl7forwardIT0_Efp0_EEOS0_OS1_ (aka: decltype (({parm#1}<{parm#2})?((forward<int>)({parm#1})) : ((forward<int>)({parm#2}))) Min<int, int>(int&&, int&&)). In other words, the expression used to deduce the return type is part of the mangled name. Personally, I expected something slightly more sane along the lines of: _Z3MinIiiET_OS0_OT0_ (aka: int Min<int, int>(int&&, int&&)). Why is this not the case?


It seems that g++ only puts the decltype expression in cases where it is actually needed, as these forms are both _Z3Maxii:

  • auto Max(int x, int y) -> int
  • auto Max(int x, int y) -> decltype(0)
Was it helpful?

Solution 2

If you overload function templates, the functions produced by these function templates (called a function template specialization) need to be different. Hence the C++ Standard specifies that the signature of function template specializations include the signature of the function template from which the specialization was generated.

Otherwise if both templates would instantiate functions that have the same function type, they would clash.

OTHER TIPS

gcc is using the "Italium C++ ABI" for mangling, and it specifies that

If the operand expression of decltype is not instantiation-dependent then the resulting type is encoded directly. For example:

      int x;
      template<class T> auto f(T p)->decltype(x);
        // The return type in the mangling of the template signature
        // is encoded as "i".
      template<class T> auto f(T p)->decltype(p);
        // The return type in the mangling of the template signature
        // is encoded as "Dtfp_E".
      void g(int);
      template<class T> auto f(T p)->decltype(g(p));
        // The return type in the mangling of the template signature
        // is encoded as "DTcl1gfp_E".

The 3rd example is a reduced version of OP's, which also encode the whole expression directly, because it is instantiation-dependent. Instantiation-dependent is defined as:

An expression is instantiation-dependent if it is type-dependent or value-dependent, or it has a subexpression that is type-dependent or value-dependent. For example, if p is a type-dependent identifier, the expression sizeof(sizeof(p)) is neither type-dependent, nor value-dependent, but it is instantiation-dependent (and could turn out to be invalid if after substitution of template arguments p turns out to have an incomplete type). Similarly, a type expressed in source code is instantiation-dependent if the source form includes an instantiation-dependent expression. For example, the type form double[sizeof(sizeof(p))] (with p a type dependent identifier) is instantiation-dependent.

The key point is that instantiation-dependent expressions "could turn out to be invalid after substitution", which is likely the reason they are left in the unevaluated form in the mangling.

I was confused by https://stackoverflow.com/a/13296666/53974, so I made a few experiments, which confirmed the answer.

As long as two templates are different, their specializations can coexist even when overload resolution cannot pick between them; so the mangled name includes the template signature.

Since overload resolution cannot pick, the latest relevant declaration in scope appears to shadow the earlier ones. In the example below, this is visible twice — notfun1 and notfun have identical source yet call different specializations, and template void fun<long>(long); refers to different templates in the two instances. (I've confirmed both by inspecting disassemblies of this source and variants thereof).

template<typename T> T var = {};
template long var<long>;
// long var; // Error

void fun(long) {}

template<typename T> void fun(T) {}
template void fun<long>(long); // void fun<long>(long)

void notfun1() {
  fun(1L);
  fun<long>(2); // Calls void fun<long>(long)
}

template<typename T> struct identity { using type = T; };
template<typename T> void fun(typename identity<T>::type);
template void fun<long>(long); // Generates void fun<long>(identity<long>::type)
//template void fun<long>(typename identity<long>::type); //Ditto, can't write both

void notfun() {
  fun(1L);
  fun<long>(2); // Calls void fun<long>(identity<long>::type)

}

template<typename T> void fun(typename identity<T>::type) {}

int main() {}

Below, for convenience, the disassembly on Mac x86_64; if you do look, you'll want to focus on the callq targets.

$ c++filt __Z3funIlEvN8identityIT_E4typeE
void fun<long>(identity<long>::type)

$ c++filt __Z3funIlEvT_
void fun<long>(long)

$ g++ -fvisibility=hidden -std=c++17 spec.cpp -o spec; objdump -C --disassemble spec

spec:   file format Mach-O 64-bit x86-64


Disassembly of section __TEXT,__text:

0000000100003f40 fun(long):
100003f40: 55                           pushq   %rbp
100003f41: 48 89 e5                     movq    %rsp, %rbp
100003f44: 48 89 7d f8                  movq    %rdi, -8(%rbp)
100003f48: 5d                           popq    %rbp
100003f49: c3                           retq
100003f4a: 66 0f 1f 44 00 00            nopw    (%rax,%rax)

0000000100003f50 void fun<long>(long):
100003f50: 55                           pushq   %rbp
100003f51: 48 89 e5                     movq    %rsp, %rbp
100003f54: 48 89 7d f8                  movq    %rdi, -8(%rbp)
100003f58: 5d                           popq    %rbp
100003f59: c3                           retq
100003f5a: 66 0f 1f 44 00 00            nopw    (%rax,%rax)

0000000100003f60 notfun1():
100003f60: 55                           pushq   %rbp
100003f61: 48 89 e5                     movq    %rsp, %rbp
100003f64: bf 01 00 00 00               movl    $1, %edi
100003f69: e8 d2 ff ff ff               callq   -46 <__Z3funl>
100003f6e: bf 02 00 00 00               movl    $2, %edi
100003f73: e8 d8 ff ff ff               callq   -40 <__Z3funIlEvT_>
100003f78: 5d                           popq    %rbp
100003f79: c3                           retq
100003f7a: 66 0f 1f 44 00 00            nopw    (%rax,%rax)

0000000100003f80 notfun():
100003f80: 55                           pushq   %rbp
100003f81: 48 89 e5                     movq    %rsp, %rbp
100003f84: bf 01 00 00 00               movl    $1, %edi
100003f89: e8 b2 ff ff ff               callq   -78 <__Z3funl>
100003f8e: bf 02 00 00 00               movl    $2, %edi
100003f93: e8 08 00 00 00               callq   8 <__Z3funIlEvN8identityIT_E4typeE>
100003f98: 5d                           popq    %rbp
100003f99: c3                           retq
100003f9a: 66 0f 1f 44 00 00            nopw    (%rax,%rax)

0000000100003fa0 void fun<long>(identity<long>::type):
100003fa0: 55                           pushq   %rbp
100003fa1: 48 89 e5                     movq    %rsp, %rbp
100003fa4: 48 89 7d f8                  movq    %rdi, -8(%rbp)
100003fa8: 5d                           popq    %rbp
100003fa9: c3                           retq
100003faa: 66 0f 1f 44 00 00            nopw    (%rax,%rax)

0000000100003fb0 _main:
100003fb0: 55                           pushq   %rbp
100003fb1: 48 89 e5                     movq    %rsp, %rbp
100003fb4: 31 c0                        xorl    %eax, %eax
100003fb6: 5d                           popq    %rbp
100003fb7: c3                           retq
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top