Почему эта переменная функция неоднозначна?
Это связано с моим предыдущим постом. Я хотел бы знать, почему одна попытка решения не сработала.
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
типовые утилиты для накопления калькулятора.
Проблема здесь в том, что он также перехватывает пустые списки, поэтому, поскольку у вас есть пустая перегрузка в качестве привязки рекурсии, он видит оба и не знает, какой выбрать.