Почему шаблон с выведенным типом возврата не перегружается другими версиями?

Почему следующие два шаблона несовместимы и не могут быть перегружены?

#include <vector>

template<typename T>
auto f(T t) { return t.size(); }
template<typename T>
auto f(T t) { return t.foobar(); }

int main() {
f(std::vector<int>());
}

Я думаю, что они (более или менее) эквивалентны следующему, что прекрасно компилируется (как мы не можем сделать decltype auto(t.size()) Я не могу дать точный эквивалент без шума ..).

template<typename T>
auto f(T t) -> decltype(t.size() /* plus some decay */) { return t.size(); }

template<typename T>
auto f(T t) -> decltype(t.foobar() /* plus some decay */) { return t.foobar(); }

Clang и GCC жалуются main.cpp:6:16: error: redefinition of 'f' если я опущу завершающий тип возврата, однако.

(Обратите внимание, что это обоснование вопрос. Я не ищу место в Стандарте, которое определяет это поведение — которое вы также можете включить в свой ответ, если хотите — но для объяснения того, почему это поведение желательно или статус-кво).

12

Решение

счислимый Тип возврата явно не может быть частью подписи.
Однако выводит выражение, которое определяет тип возвращаемого значения (и участвует в SFINAE) из return У заявлений есть некоторые проблемы. Допустим, мы должны были взять первый return выражение оператора и вставьте его в некоторый скорректированный, виртуальный тип трейлинг-возврата:

  1. Что делать, если возвращаемое выражение зависит от местный декларации? Это не обязательно останавливает нас, но это чрезвычайно нарушает правила. Не забывайте, что мы не можем использовать имена объявленных объектов; Это может потенциально усложнить наш тип «трейлинг-возврат» на высочайшем уровне без какой-либо выгоды.

  2. Популярным вариантом использования этой функции являются возвращаемые шаблоны функций. лямбды. Однако вряд ли мы можем сделать лямбду частью подписи — возникающие сложности были подробно рассмотрены ранее. Только калечить потребует героических усилий. Следовательно, мы должны были бы исключить шаблоны функций, используя лямбды.

  3. подпись декларации не может быть определено, если это не определение, также представляет целый ряд других проблем. Самым простым решением было бы полностью запретить (не определить) объявления таких шаблонов функций, что почти смешно.

К счастью, автор N3386 постарался сохранить правила (и реализацию!) Простыми. Я не могу себе представить, как отсутствие необходимости в написании типа «трейлинг-возврат» в некоторых угловых случаях оправдывает такие дотошные правила.

6

Другие решения

Я думаю, что это может быть миссис комисс, но предыстория, по-моему, такова

  1. Вы не можете перегрузить тип возвращаемого значения функции. Это означает, что в декларации

    template<typename T>
    auto f(T t) { return t.size(); }
    

    Ценность auto Фактически не интересен компилятору до момента создания функции. Очевидно, что компилятор не добавляет некоторую проверку SFINAE в тело функции, чтобы проверить, T::size существует, как это не во всех других случаях, когда T используется внутри тела функции

  2. При генерации перегрузок компилятор проверяет, являются ли две сигнатуры функций точными эквивалентными, принимая во внимание все возможные замены.

    В первом случае компилятор получит что-то вроде

    [template typename T] f(T)
    [template typename T] f(T)
    

    Это точный эквивалент

    Во втором случае, однако, как decltype указано явно, он будет добавлен к аргументам шаблона, так что вы получите

    [template typename T, typename = typeof(T::size())] f(T)
    [template typename T, typename = typeof(T::size())] f(T)
    

    Это не точные эквиваленты, очевидно.

    Таким образом, компилятор откажется от первого случая, а второй может быть ОК при замене реального типа вместо T,

1

Глядя на символы, созданные моим компилятором:

[tej@archivbox ~]$ cat test1.cc

#include <vector>

template<typename T>
auto JSchaubStackOverflow(T t) { return t.size(); }

// template<typename T>
// auto f(T t) { return t.foobar(); }

int do_something() {
JSchaubStackOverflow(std::vector<int>());
return 4;
}
[tej@archivbox ~]$ c++ -std=c++14 -pedantic test1.cc -c -o test1.o
[tej@archivbox ~]$ nm test1.o | grep JScha
0000000000000000 W _Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDaT_
[tej@archivbox ~]$ nm -C test1.o | grep JScha
0000000000000000 W auto JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >)
[tej@archivbox ~]$ cat test2.cc

#include <vector>

template<typename T>
auto JSchaubStackOverflow(T t) -> decltype(t.size() /* plus some decay */) { return t.size(); }

template<typename T>
auto JSchaubStackOverflow(T t) -> decltype(t.foobar() /* plus some decay */) { return t.foobar(); }
struct Metallica
{

Metallica* foobar() const
{
return nullptr;
}
};int do_something() {
JSchaubStackOverflow(std::vector<int>());
JSchaubStackOverflow(Metallica());
return 4;
}
[tej@archivbox ~]$ c++ -std=c++14 -pedantic test2.cc -c -o test2.o
[tej@archivbox ~]$ nm test2.o | grep JScha
0000000000000000 W _Z20JSchaubStackOverflowI9MetallicaEDTcldtfp_6foobarEET_
0000000000000000 W _Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDTcldtfp_4sizeEET_
[tej@archivbox ~]$ nm -C test2.o | grep JScha
0000000000000000 W decltype (({parm#1}.foobar)()) JSchaubStackOverflow<Metallica>(Metallica)
0000000000000000 W decltype (({parm#1}.size)()) JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >)

Из этого вы можете видеть, что decltype (что угодно) может помочь нам различать символы, это часть подписи. Но «авто» нам не помогает …
Таким образом, если бы vector имел и метод foobar, и метод size, обе перегрузки JSchaubStackOverflow были бы искажены как Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDaT
Теперь я оставлю кому-то еще, чтобы найти соответствующий раздел в ISO о сигнатурах шаблонных функций.

—РЕДАКТИРОВАТЬ—
Я знаю, что у него уже есть принятый ответ, но для справки, вот техническая трудность — объявления без определений:

[tej@archivbox ~]$ cat test2.cc

#include <vector>

template<typename T>
auto JSchaubStackOverflow(T t) -> decltype(t.size());

template<typename T>
auto JSchaubStackOverflow(T t) -> decltype(t.foobar());

struct Metallica
{

Metallica* foobar() const
{
return nullptr;
}
};int do_something() {
JSchaubStackOverflow(std::vector<int>());
JSchaubStackOverflow(Metallica());
return 4;
}
[tej@archivbox ~]$ c++ -std=c++14 -pedantic test2.cc -c -o test2.o
[tej@archivbox ~]$ nm -C test2.o | grep JScha
U decltype (({parm#1}.foobar)()) JSchaubStackOverflow<Metallica>(Metallica)
U decltype (({parm#1}.size)()) JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >)

Это означает, что можно делать все без функциональных тел. Специализации шаблона будут даны в другом модуле перевода, но для этого компоновщик должен их найти … таким образом, нельзя перегружать тело функции.

1

«Только ошибки в типах и выражениях в непосредственном контексте типа функции или ее типов параметров шаблона являются ошибками SFINAE.

Если оценка замещенного типа / выражения вызывает побочный эффект, такой как создание некоторой специализации шаблона, генерация неявно определенной функции-члена и т. Д., Ошибки в этих побочных эффектах рассматриваются как серьезные ошибки. » источник

Ваше первое объявление вызывает неявную замену возвращаемого типа и, следовательно, не соответствует SFINAE.

1
По вопросам рекламы [email protected]