Подтвердить что ты не робот

Почему возвращаемый тип экземпляров шаблонов функций С++ включен в имя функции 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 не работает?

(Я предполагаю, что есть причина, и это не просто произвольный выбор.)

4b9b3361

Ответ 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.