Правило вычета параметра шаблона типа функции C ++

Следующий код при сборке с

clang -Wall main.cpp -o main.o

генерирует следующую диагностику (после кода):

template <typename F>
void fun(const F& f)
{

}

template <typename F>
void fun(F f)
{

}

double Test(double d) { return d; }

int main(int argc, const char * argv[])
{
fun(Test);

return 0;
}

диагностика:

main.cpp:17:5: error: call to 'fun' is ambiguous
fun(Test);
^~~
main.cpp:2:6: note: candidate function [with F = double (double)]
void fun(const F& f)
^
main.cpp:8:6: note: candidate function [with F = double (*)(double)]
void fun(F f)
^
1 error generated.

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

Тем не менее, когда мы меняем вызов fun(Test) быть fun(&Test) явно взять адрес функции (или явный указатель функции), тогда оба fun разрешить параметр шаблона F быть double (*)(double)!

Такое поведение, похоже, является общим для всех Clang и GCC (и Visual Studio 2013).

Тогда возникает вопрос: каково правило вывода параметров шаблона типа функции для шаблонных функций в формах, приведенных в моем примере кода?

PS: если мы добавим еще один экземпляр fun принять F* fтогда, похоже, что правило перегрузки просто решает выбрать эту версию, и о двусмысленности вообще не сообщается (хотя, как я уже говорил, неоднозначность не самая большая проблема ранее, но в этом последнем случае я удивляюсь, почему третья версия здесь лучше всего подходит?)

template <typename F>
void fun(F* f)
{
}

10

Решение

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

Нельзя копировать переменную типа функции вокруг, поэтому в template <typename F> void fun(F f), F не может иметь тип функции.

Однако переменная типа функции может быть преобразована в указатель на тип функции (это называется «распадом», как преобразование массива в указатель), поэтому при сопоставлении типа функции с template <typename F> void fun(F f), F должен быть указателем на функцию.

Когда речь идет о ссылке на тип функции, затухание функции от указателя не может произойти (я не могу найти это в Стандарте, но это должно быть описано вместе с правилами ссылки на массив), поэтому при сопоставлении шаблона <typename F> void fun(const F& f), F является типом функции (а тип параметра — ссылка на функцию).

2

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

Возможно, вы уже поняли это, так как прошло почти 3 года с тех пор, как вы опубликовали этот вопрос. Но я дам свой ответ, если вы этого не сделаете.

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

Прежде всего, имейте в виду, что массивы и функции странны тем, что массив может неявно распадаться на указатель на свой первый элемент, а функции могут неявно распадаться на указатели на функции. И хотя синтаксически допустимо, параметры функции на самом деле не могут быть массивом или типом функции, но указателями, Значения параметров функции могут быть записаны с типом массива или функций, но компиляторы рассматривают такие типы как указатели. Например, посмотрите на код ниже:

int val [3]; //type of val is 'int [3]'
int * pval = val; //type of pval is 'int *'
//the assignment is correct since val can decay into 'int *'

double foo(double); //type of foo is 'double (double)'
double (*pfoo) (double); // type of pfoo is 'double (*)(double)'
pfoo = foo; //correct since functions can decay into function pointers.

void bar(int x []); // syntax is correct
// but compilers see the type of x as 'int *'

void bar(int x(int));// again syntax is correct
// but compilers see the type of x as 'int (*)(int)'

тем не мение, вещи становятся еще более странными, когда параметр функции имеет ссылочный тип. Параметр функции, имеющий тип ссылки на массив / функцию, считается имеющим тип ссылки на массив / функцию, не тип указателя. Например:

void bar(int (& x)[2]); //type of x is now 'int (&) [2]'
void bar(int (& x)(int)); //type of x is now 'int (&)(int)'

Относительно вашего первого вопроса, так как тип параметра в вашей первой функции (fun(const F& f)) включает в себя ссылку, тип f будет выводиться как ссылка на функцию, когда функция передается в качестве аргумента; точнее, выведенный тип f будет double (&) (double), С другой стороны, поскольку тип параметра второй функции не содержит ссылку (fun(F f)), компиляторы неявно выводят тип f в качестве указателя на функцию (выведенный тип f будет double (*)(double)), когда функция передается в качестве аргумента.

Тем не менее, когда мы меняем вызов fun(Test) быть fun(&Test) чтобы явно взять адрес функции (или явный указатель на функцию), тогда оба веселья разрешат параметр шаблона F в double (*)(double)!

Хорошо, теперь, так как вы явно передаете тип указателя на функцию в качестве аргумента (беря адрес Test), выведенный тип f должен иметь указатель. Однако ссылка и постоянство параметра первой функции не игнорируются.. когда fun(&Test) выполняется, выведенный тип f для первой функции будет double (* const &) (double) и выведенный тип f для второй функции будет double (*) (double),

PS: если мы добавим еще один экземпляр fun принять F* fтогда, похоже, что правило перегрузки просто решает выбрать эту версию, и о двусмысленности вообще не сообщается (хотя, как я уже говорил, неоднозначность не самая большая проблема ранее, но в этом последнем случае я удивляюсь, почему третья версия здесь лучше всего подходит?)

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

РЕДАКТИРОВАТЬ:
Я дал очень неаккуратный ответ на вопрос: почему нет никакой двусмысленности, когда третья функция (fun(F * f)) добавлен. Ниже ясный ответ, я надеюсь.

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

Теперь давайте снова укажем на специализации шаблона для аргумента Test, Как упоминалось выше, после вывода типа шаблона специализация шаблона первого функционального шаблона void fun(double (&f) (double) ) и что из второго шаблона функции void fun(double (*f) (double) ), На основе необходимых преобразований из типа аргумента double (double) к типам параметров возможных шаблонных функций double (&) (double) а также double (*) (double) соответственно, они оба считаются точными совпадениями, поскольку необходимы только тривиальные преобразования. Следовательно, правила частичного заказа используются для определения того, какой из них является более специализированным. Оказывается, ни то, ни другое, поэтому выдается ошибка неоднозначности.

Когда вы добавили третий шаблон функции (void fun(F * f)), вычет типа шаблона приводит к специализации шаблона как void fun(double (*f)(double), Как и раньше, все три шаблонные функции одинаково хорошо совпадают (на самом деле они точно совпадают). Из-за этого снова, правила частичного заказа используются в качестве крайней меры, и получается, что третий шаблон функции является более специализированным и, таким образом, он подобран.

Обратите внимание на тривиальные преобразования: Хотя это и не завершено, следующие преобразования из типа аргумента в тип параметра считаются тривиальным преобразованием (с учетом типа T):

  1. от T в const T
  2. от T в T &
  3. или от массивов или типов функций к их соответствующим типам указателей (распадам, которые упомянуты в начале).

РЕДАКТИРОВАТЬ № 2: Кажется, что я могу не использовать правильные формулировки, поэтому просто чтобы понять, что я имею в виду под шаблон функции это шаблон, который создает функции и функция шаблона функция, которая создается шаблоном.

5