Почему вывод аргумента шаблона завершается неудачно с перегруженной функцией? - PullRequest
4 голосов
/ 06 июня 2019

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

Я использовал enable_if, чтобы была допустима только одна перегрузка, но это не помогло.

#include <string>
#include <type_traits>

void foo(int, int){}
void foo(std::string, std::string) {}

template <bool Val1, bool Val2, bool ...Rest>
struct And
{
    enum {value = And<Val1 && Val2, Rest...>::value};
};
template <bool Val1, bool Val2>
struct And<Val1, Val2>
{
    enum {value = Val1 && Val2};
};

template <typename ...Params, typename ...Args, typename = typename std::enable_if<
    And<std::is_convertible<Args, Params>::value...>::value
>::type>
void Invoke(void (*fn)(Params...), Args ...args){}

int main() {
    Invoke(&foo, "a", "b");
    return 0;
}

Попробуйте ideone .

Компилятор жалуется на mismatched argument pack lengths while expanding ‘std::is_convertible<Args, Params>::value’, когда присутствуют обе перегрузки.Когда я закомментирую перегрузку int, программа прекрасно компилируется, а когда я закомментирую перегрузку std::string, дедукция завершается неудачно, как и должно быть, поскольку const char[] неявно не преобразуется в int.

Стандарт (раздел 17.8.2.1.6.2 стандарта C ++ 17) гласит следующее:

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

Поэтому я ожидаю, что компилятор попытается перегрузить int, где вывод будетпотерпеть поражение.Когда он пытается перегрузить std::string, вычет будет успешным.

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

Где я ошибаюсь?

Ссылки на стандарт будут приветствоваться.

Ответы [ 2 ]

5 голосов
/ 06 июня 2019

Проблема здесь в том, что работает более одной функции.Вычитание Params... произойдет еще до того, как вы попадете в часть шаблона SFINAE.Когда он пытается вывести Params.. из void (*fn)(Params...), он соответствует void foo(int, int) и void foo(std::string, std::string).Поскольку он находит несколько совпадений, 17.8.2.1.6.2 заявляет, что он обрабатывается как не выводимый контекст.

Поскольку он не может вывести тип, вы получаете ошибку жесткого останова.SFINAE происходит только после шага вывода параметров шаблона, чего он не может получить в этом случае.

5 голосов
/ 06 июня 2019

&foo не указатель функции, а набор перегрузки.Вы должны быть в явном виде:

Invoke(static_cast<void(*)(std::string, std::string)>(&foo), "a", "b");

Чтобы упростить ошибочный enable_if, вы можете взять неуказанный тип указателя на функцию с пакетом вариационных пакетов и проверить is_invocable: https://en.cppreference.com/w/cpp/types/is_invocable

...