Странное несоответствие между шаблоном функции и «нормальной» функцией - PullRequest
4 голосов
/ 11 июля 2019

У меня есть две почти одинаковые функции (за исключением того, что одна из них является шаблоном):

int* bar(const std::variant<int*, std::tuple<float, double>>& t)
{
    return std::get<0>(t);
}
template <typename... Args>
int* foo(const std::variant<int*, std::tuple<Args...>>& t)
{
    return std::get<0>(t);
}

Чем они используются так:

foo(nullptr);
bar(nullptr);

Второй компилирует и возвращает (int*)nullptr, но первый - нет (в Visual Studio 2019 с использованием C ++ 17 выдается ошибка foo: no matching overload found). Зачем? Почему превращение этой функции в шаблон приводит к тому, что она перестает компилироваться?

Использование foo, как показано ниже, также не помогает, поэтому невозможность вывести Args, вероятно, не является проблемой:

foo<>(nullptr);

И наоборот, работает следующее:

foo(std::variant<int*, std::tuple<>>(nullptr));

Можно ли как-то избежать необходимости писать это так долго?

Ответы [ 3 ]

4 голосов
/ 11 июля 2019

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

* Источник: 1006 *

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

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

template<class ... Types> void f(Types ... values);
void g() {
  f<int*, float*>(0, 0, 0); // Types = {int*, float*, int}
}

Это также объясняет, почему foo<>(nullptr); по-прежнему не работает. Поскольку компилятор пытается определить дополнительные типы для расширения Args, в этом случае, похоже, нет никакой разницы между foo(nullptr); и foo<>(nullptr);.

2 голосов
/ 11 июля 2019

Когда рассматривается шаблонная функция, она будет работать только для точного соответствия типов аргументов при вызове. Это означает, что никакие преобразования не будут сделаны (за исключением квалификаторов cv).

Простой обходной путь в вашем случае - заставить функцию перехватить std::nullptr_t и переслать ее в ваш шаблон.

int* foo(std::nullptr_t) {
    return foo(std::variant<int*, std::tuple<>>{nullptr});
}
1 голос
/ 11 июля 2019

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

template <typename... Args>
int *foo(const ::std::variant<int*, ::std::tuple<Args...>> &t)
{
    return ::std::get<0>(t);
}

int *foo(int *ip)
{
    using my_variant = ::std::variant<int *, ::std::tuple<>>;
    return foo(my_variant{ip});
}

template <typename... Args>
int *foo(::std::tuple<Args...> const &t)
{
    using my_variant = ::std::variant<int *, ::std::tuple<Args...>>;
    return foo(my_variant{t});
}

template <typename... Args>
int *foo(::std::tuple<Args...> &&t)
{
    using my_variant = ::std::variant<int *, ::std::tuple<Args...>>;
    return foo(my_variant{::std::move(t)});
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...