Почему эта переменная функция неоднозначна?

Это связано с моим предыдущим постом. Я хотел бы знать, почему одна попытка решения не сработала.

template <typename... T>             /* A */
size_t num_args ();

template <>
size_t num_args <> ()
{
    return 0;
}

template <typename H, typename... T> /* B */
size_t num_args ()
{
    return 1 + num_args <T...> ();
}

Если я попытаюсь позвонить, скажем, num_args<int,float>() тогда ошибка в том, что вызов функции неоднозначен:

  • A с T = {int, float}
  • B с H=int, T={float}

Я не понимаю, как это неоднозначно - A является объявлением, а B является определением функции, объявленной A. Верно?

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

Если это так, то какой смысл вариационных свободных функций? Что они могут сделать?

4 ответа

Решение

Я не понимаю, как это неоднозначно - A является объявлением, а B является определением функции, объявленной A. Верно?

Нет. A - это объявление шаблона функции, а B - это объявление (и определение) другого шаблона функции.

Компилятор не может выбирать между двумя: у них обоих нет аргументов, а аргументы шаблона совпадают для обоих.

Посередине - полная специализация шаблона функции, объявленного в A.

Если вы пытались сделать B другой специализацией A:

template <typename H, typename... T> /* B */
size_t num_args<H, T...>()
{
    return 1 + num_args <T...> ();
}

... вы бы получили частичную специализацию шаблона функции, что недопустимо.

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

template <typename... T>
class Num_Args;

template <>
struct Num_Args <>
{
    static constexpr size_t calculate() {
        return 0;
    }
};

template <typename H, typename... T>
struct Num_Args <H, T...>
{
    static constexpr size_t calculate() {
        return 1 + Num_Args<T...>::calculate();
    }
};

template <typename... T> /* B */
constexpr size_t num_args ()
{
    return Num_Args<T...>::calculate();
}

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

size_t num_args()
{
    return 0;
}

template <typename H, typename... T> /* B */
size_t num_args (H h, T... t)
{
    return 1 + num_args(t...);
}


РЕДАКТИРОВАТЬ:

Насколько я вижу, следующие злоупотребления enable_if должен работать как решение вашего первоначального вопроса:

#include <utility>

// Only select this overload in the empty case 
template <typename... T>
typename std::enable_if<(sizeof...(T) == 0), size_t>::type
num_args() 
{ 
    return 0;
}

template <typename H, typename... T>
size_t
num_args() 
{
    return 1 + num_args<T...>();
}

(Edit2: обратный порядок перегрузок, чтобы код на самом деле компилировался)

Хотя мне очень нравится std::enable_if<sizeof...(T) == 0, _>hack by JohannesD, я все равно добавлю приведенный ниже хак, который я на самом деле не помню, откуда я узнал, который разрешает двусмысленность только с бесплатными функциями без использования классов:

      template <typename One>
size_t num_args() {
    return 1;
}

template <typename First, typename Next, typename... Rest>
size_t num_args() {
    return 1 + num_args<Next, Rest...>();
}

Частичная специализация не требуется, и он полностью исключает случай с нулевым параметром, заканчивая рекурсию распаковки на одном параметре. Кроме того, см. /questions/35972466/rekursivnyij-shablon-peremennoj-dlya-raspechatki-soderzhimogo-paketa-parametrov/55210223#55210223 для еще более приятного решения С++17, в котором используется та же идея.

Единственная загвоздка в том, что он на самом деле не распространяется на случай нулевого параметра шаблона, что невозможно в контексте бесплатных функций AFAIK. Если это требуется с точки зрения потребителя, необходимо использовать частично специализированный класс, см. Ответ Р. Мартиньо Фернандеса.

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

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

Другие вопросы по тегам