It seems to be a matter of ABI. For instance, the Itanium C++ ABI reads:
If the parameter type is non-trivial for the purposes of calls, the caller must allocate space for a temporary and pass that temporary by reference.
And, further:
A type is considered non-trivial for the purposes of calls if it has a non-trivial copy constructor, move constructor, or destructor, or all of its copy and move constructors are deleted.
The same requirement is in AMD64 ABI Draft 1.0.
For instance, in libstdc++, std::tuple
has non-trivial move constructor: https://godbolt.org/z/4j8vds. The Standard prescribes both copy and move constructor as defaulted, which is satisfied here. However, at the same time, tuple
inherits from _Tuple_impl
and _Tuple_impl
has a user-defined move constructor. Consequenlty, move constructor of tuple
itself cannot be trivial.
On the contrary, in libc++, both copy and move constructors of std::tuple<int>
are trivial. Therefore, the argument is passed in a register there: https://godbolt.org/z/WcTjM9.
As for Microsoft STL, std::tuple<int>
is trivially neither copy-constructible nor move-constructible. It even seems to break the C++ Standard rules. std::tuple
is defined recursively and, at the end of recursion, std::tuple<>
specialization defines non-defaulted copy constructor. There is a comment about this issue: // TRANSITION, ABI: should be defaulted
. Since tuple<>
has no move constructor, both copy and move constructors of tuple<class...>
are non-trivial.