Почему возвращаемый тип экземпляров шаблонов функций С++ включен в имя функции mangled?
The Itanium ABI указывает, что с несколькими неинтересными исключениями тип возврата включается в искаженные имена шаблонных экземпляров, но не не-шаблоны.
Почему это? В каком случае у вас может быть два экземпляра шаблонов функций, в которых линкер должен различать их, потому что это не указывает на нарушение правил одного определения или подобное?
В качестве примера я имею в виду:
class ReturnType {};
class ParamType {};
template <typename T>
ReturnType foo(T p) {
return ReturnType();
};
template ReturnType foo<ParamType>(ParamType);
ReturnType bar(ParamType p) {
return ReturnType();
}
Тогда в результирующем объектном файле есть manglings:
ReturnType foo<ParamType>(ParamType)
=> _Z3fooI9ParamTypeE10ReturnTypeT_
^^^^^^^^^^^^
ReturnType bar(ParamType)
=> _Z3bar9ParamType
Почему foo
нужен ReturnType
искаженный, но bar
не работает?
(Я предполагаю, что есть причина, и это не просто произвольный выбор.)
Ответы
Ответ 1
Может быть, потому что, в отличие от обычных функций, подпись шаблонов функций содержит тип возврата? §1.3:
1.3.17 подпись <
имя функции >
, список типов параметров (8.3.5) и вложенное пространство имен (если есть)
[Примечание: Подписи используется как основа для обозначения и привязки имени. - примечание окончания)
1.3.18 подпись <
шаблон функции >
имя, список типов параметров (8.3.5), охватывающее пространство имен (если есть), return тип и список параметров шаблона
Учтите, что мы можем иметь две полностью отличные перегрузки шаблонов функций, которые отличаются только по типу возвращаемого типа, если они написаны следующим образом:
template <int>
char foo();
template <int>
int foo();
Если определение имени не учитывало бы тип возврата, связывание этих шаблонов оказалось бы трудным, так как foo<0>
однозначно не упоминает одну специализацию. Тем не менее, одна специализация может быть решена с использованием разрешения перегрузки (без аргументов):
int (*funptr)() = foo<0>;
С другой стороны, включение типа возврата не требуется для обычных функций, поскольку они не могут быть перегружены по их возвращаемому типу - т.е. их подпись не включает тип возврата.
Ответ 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<void>()
, но они не могут использовать одно и то же поврежденное имя, иначе не было бы возможности для сгенерированной сборки для main
указать о котором он ссылается.
Как правило, если у вас есть перегруженная функция шаблона, для конкретного аргумента шаблона будет использоваться только одно из определений, так что это необычно, но в комментариях к ответу от Columbo dyp придумал основную идею о том, как это может быть действительно полезным. В Может ли addressof() быть реализована как функция constexpr?, я придумал
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);
}
но на самом деле это нарушение ODR, если в нескольких единицах перевода используется одно и то же экземпляр addressof<X>
, где X
является неполным, а некоторое, где X
является полным и имеет перегруженный оператор &
. Это можно переработать, выполнив логику внутри 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<X>
ссылается на другую функцию, чем при завершении X
.