I understand that after a typename, a single &
means an lvalue reference, and a double &&
means an rvalue reference or a "deduced reference", also called a universal reference by Scott Meyers. But I have never seen a triple &&&
in a function or method signature. What does it stand for?
The following code produces a method with triple &&&
:
#include <iostream>
#include <utility>
template<typename ostream> void foo(ostream&& out) {
out << "foo" << std::endl;
}
template<typename ostream> void bar(ostream&& out) {
foo(std::forward<ostream>(out));
}
int main() {
bar(std::cout);
}
After compiling the code with g++-4.8.1
, I run nm -j a.out | c++filt
. The -j
switch is (I believe) nonstandard, it means just display the symbol names (no value or type). I get this:
__GLOBAL__sub_I_triple_ampersand.cpp
void bar<std::ostream&>(std::ostream&&&)
void foo<std::ostream&>(std::ostream&&&)
__static_initialization_and_destruction_0(int, int)
std::ostream::operator<<(std::ostream& (*)(std::ostream&))
std::ios_base::Init::Init()
std::ios_base::Init::~Init()
std::cout
std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
std::ostream&&& std::forward<std::ostream&>(std::remove_reference<std::ostream&>::type&)
std::piecewise_construct
std::__ioinit
std::basic_ostream<char, std::char_traits<char> >& std::operator<<<std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
___cxa_atexit
__mh_execute_header
_main
dyld_stub_binder
I get similar output when I compile with clang++
. So my question is, what does the triple ampersand stand for? Apparently I cannot write &&&
in my code directly. Is it because my c++filt
is demangling the symbols incorrectly? I am using system provided c++filt
on Mac OS X 10.8.
The fact that you name your template parameter ostream
and instantiate it with std::ostream&
is confusing.
There is a minor bug in the demangling (it should have put a space to break the group of &
as its meaning is & &&
and not && &
), but the presence of the consecutive &
themselves is not a problem: another way to demangle the name would have been void bar<$T=std::ostream$&>($T$&&)
, i.e. the mangled name contains backreference to the parameters and not an expansion of the type itself.
Why? In general, the mangled names of instantiations have to represent the precise instantiated template and its paramaters (as opposed to use the signature resulting from things like the collapsing rule or expanding references to traits) as instatiation of two different function template of the same name resulting in the same signature can validly coesist in a program (I don't think they can in a compilation unit, but name look up and overload resolution is so complex in C++ that I may be mistaken) and using a simplified signature would merge them inadequately. And example with traits, I can't currently think of one with the reference collapsing rules(*), the two function templates which follow may be overloaded in a program (even in the same CU)
template <typename T>
struct Traits{};
template <typename T>
void f(typename Traits<T>::T1)
{
return;
}
template <typename T>
void f(typename Traits<T>::T2)
{
return;
}
But if you have the following specialization of Traits
template <>
struct Traits<int> {
typedef int T1;
typedef int T2;
};
you can't use f<int>(42)
at a place where both are visible as there is an ambiguity, but by making them selectively visible you could use one in a CU and the other in another CU, so the mangling has to be able to make a difference.
(*) It is possible that there is none, I don't remember having seen one mentionned and as soon as the mechanism has to be in place (another reason is to limit the size of mangled names), it's safer and also probably easier ato continue to use it for everything.