Частичный вывод аргумента шаблона C ++ для функции с пакетом с переменными значениями вызывает неоднозначный вызов в Clang и MSVC - PullRequest
5 голосов
/ 09 июля 2019

Рассмотрим следующий фрагмент (доступен на компиляторе epxlorer ):

template<typename T, typename... Args>
auto foo(Args&&... args) {}

template<typename... Args>
auto foo(Args&&... args) {}

int main() {
    foo<char>('a');
}

Он прекрасно компилируется для GCC и завершается неудачно как для Clang, так и для MSVC (с компилятором, говорящим неоднозначно)звоните )

Почему Clang и MSVC не смогли сделать такой, казалось бы, очевидный вывод?

РЕДАКТИРОВАТЬ: GCC предоставляет мне ожидаемое решение как пользователь, есть ли простой способ нажать Clang и MSVC, чтобы выбрать шаблон без особых изменений исходного кода?

Ответы [ 2 ]

6 голосов
/ 09 июля 2019

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

<source>(6): note: could be 'auto foo<char>(char &&)'
<source>(3): note: or       'auto foo<char,char>(char &&)'

(от MSVC; Clang похож)

В этом случае, так какПервый (единственный) параметр функции foo - это char, компилятор не может различить один параметр шаблона и две версии параметра шаблона шаблона.

Если вы измените вызов функции на

foo<char>(10);

он скомпилируется.

В спецификации языка есть пример («Частичное упорядочение шаблонов функций», [temp.func.order]), очень похожий на ваш код:

template<class T, class... U> void f(T, U...); // #1
template<class T > void f(T); // #2

void h(int i) {
    f(&i); // error: ambiguous
}

Поскольку GCC компилирует его, это ошибка в GCC.

1 голос
/ 12 июля 2019

После некоторых испытаний и с использованием упомянутой ссылки на стандарт: [temp.func.order] , [temp.deduct.partial] я пришел к следующему пониманию ситуации.

Проблема

Учитывая пример, приведенный в вопросе:

template<typename T, typename... Args> auto foo(Args&&... args) {} //#1

template<typename... Args>             auto foo(Args&&... args) {} //#2

# 2 - это функция с пакетом переменных параметров, который может быть выведен. можно быть выведено, а не должно . Таким образом, ничто не мешает пользователю явно указать аргументы шаблона. Следовательно, foo<char>('a') может быть как явным воплощением # 2, так и воплощением # 1, провоцируя двусмысленность. Стандарт не поддерживает предпочтительное соответствие между перегрузкой № 1 и № 2.

GCC вышел за рамки стандарта в своей реализации, приписав более высокое предпочтение для # 1, когда аргумент шаблона передается вручную, в то время как Clang и MSVC оставили его ванильным.

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

Решение

Вот решения, которые я нашел для моего варианта использования. (Форвардное строительство объекта или разнообразная пачка объектов)

Вариант 1

Объявите дополнительную функцию, специализирующуюся на одном аргументе, она будет иметь приоритет над переменными. (Не масштабируется и не обобщается)

template<typename T> auto foo(T&& args) {}
//or
template<typename T, typename Arg> auto foo(Arg&& arg) {}

Вариант 2

Отключить перегрузку, когда первый аргумент непустого пакета параметров совпадает с заданным типом T.

template<typename T, typename... Args>
constexpr bool is_valid() {
    if constexpr(sizeof...(Args)==0)
        return true;
    else
        return !std::is_same_v<T,std::tuple_element_t<0,std::tuple<Args...> > > ;
}

template<typename T, typename... Args, typename = std::enable_if_t<is_valid<T,Args...>()> >
auto foo(Args&&... args) {}
...