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

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

#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' Если я оставлю тип возврата трейлинга, однако.

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

4 ответов


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

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

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

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

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


Я думаю, что это может быть commitee мисс, но предыстория я считаю следующее:

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

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

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

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

    в первом случае компилятор получит smth как

    [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)
    

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

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


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

[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 о подписях функции шаблона.

--EDIT-- Я знаю, что у него уже есть принятый ответ, но для протокола, вот техническая трудность-объявления без определений:

[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> >)

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


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

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

ваше первое объявление вызывает неявное подстановка возвращаемого типа, а значит не придерживается SFINAE