Шаблон функции Variadic с расширением пакета не в последнем параметре
Мне интересно, почему следующий код не компилируется:
struct S
{
template <typename... T>
S(T..., int);
};
S c{0, 0};
Этот код не скомпилируется как с clang, так и с GCC 4.8. Вот ошибка с clang:
test.cpp:7:3: error: no matching constructor for initialization of 'S'
S c{0, 0};
^~~~~~~
test.cpp:4:5: note: candidate constructor not viable: requires 1 argument, but 2 were provided
S(T..., int);
^
Мне кажется, что это должно работать, и T следует выводить как пакет длиной 1.
Если стандарты запрещают делать подобные вещи, кто-нибудь знает, почему?
Ответы
Ответ 1
Потому что, когда пакет параметров функции не является последним параметром, пакет параметров шаблона не может быть выведен из него, и он будет проигнорирован путем вычитания аргумента шаблона.
Итак, два аргумента 0, 0
сравниваются с , int
, что дает несоответствие.
Правила дедукции, подобные этому, должны охватывать многие особые случаи (например, что происходит, когда рядом появляются два пакета параметров). Поскольку пакеты параметров являются новой функцией в С++ 11, авторы соответствующего предложения разработали правила консервативно.
Обратите внимание, что пакет параметров шаблона завершающего шаблона будет пустым, если он иначе не выводится. Поэтому, когда вы вызываете конструктор с одним аргументом, все будет работать (обратите внимание на разницу в пакете параметров шаблона и в пакете параметров функции здесь. Первый заголовок, последний - нет).
Ответ 2
Итак, должно быть обходное решение. Что-то в этом роде:
// Extract the last type in a parameter pack.
// 0, the empty pack has no last type (only called if 1 and 2+ don't match)
template<typename... Ts>
struct last_type {};
// 2+ in pack, recurse:
template<typename T0, typename T1, typename... Ts>
struct last_type<T0, T1, Ts...>:last_type<T1, Ts...>{};
// Length 1, last type is only type:
template<typename T0>
struct last_type<T0> {
typedef T0 type;
};
struct S
{
// We accept any number of arguments
// So long as the type of the last argument is an int
// probably needs some std::decay to work right (ie, to implicitly work out that
// the last argument is an int, and not a const int& or whatever)
template <typename... T, typename=typename std::enable_if<std::is_same<int, typename last_type<T...>::type>>::type>
S(T...);
};
где мы проверяем, что последний тип пакета параметров является int
или что мы только проходим .
Ответ 3
На самом деле меня немного интересует одно и то же (желая специализировать шаблонные пакеты параметров на основе последних аргументов).
Я считаю, что может быть путь вперед, комбинируя разворот кортежей (std::make_tuple
, back-port std::apply
для С++ 14 и т.д.):
Вернемся сюда, если он будет успешным.
Похожие сообщения:
ИЗМЕНИТЬ:
Да, выяснилось это немного; не идеально, так как есть дополнительные копии, летающие вокруг, но это начало.
Если вы знаете более простой способ, чем то, что я перечислю ниже, пожалуйста, не стесняйтесь публиковать сообщения!
TL; DR
Можно делать такие вещи:
auto my_func_callable = [] (auto&& ... args) {
return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
stdcustom::make_callable_reversed(my_func_callable);
И затем реализуем этот код pseduo:
template<typename ... Args>
void my_func(Args&& ... args, const my_special_types& x);
Сделав что-то вроде:
template<typename... Args>
void my_func(Args&& ... args)
-> call my_func_reversed(args...)
template<typename... RevArgs>
void my_func_reversed(const my_special_types& x, RevArgs&&... revargs)
-> do separate things with revargs and my_special_types
-> sub_func_reversed(revargs...)
Использование вышеуказанных утилит.
Есть некоторые (много) недостатков. Перечислите их ниже.
Масштаб
Это для пользователей С++ 14 (возможно, С++ 11), которые хотят заимствовать из будущего (С++ 17).
Шаг 1: Обратные аргументы
Есть несколько разных способов сделать это. Я привел некоторые альтернативы в этом примере:
Я выбрал вариант 1, так как мне легче переваривать.
Затем я попытался быстро его формализовать:
Определение фрагментов (адаптация С++ 17 возможной реализации std::apply
на cppreference.com):
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_reversed_impl(F &&f,
Tuple &&t, std::index_sequence<I...>)
{
// @ref https://stackoverflow.com/a/31044718/7829525
// Credit: Orient
constexpr std::size_t back_index = sizeof...(I) - 1;
return f(std::get<back_index - I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply_reversed(F &&f, Tuple &&t)
{
// Pass sequence by value to permit template inference
// to parse indices as parameter pack
return detail::apply_reversed_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<
std::tuple_size<std::decay_t<Tuple>>::value>{});
}
Использование фрагментов: (из tuple_future_main.output.txt
, скопировано сверху)
auto my_func_callable = [] (auto&& ... args) {
return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
stdcustom::make_callable_reversed(my_func_callable);
Шаг 2: Пристегните башмак (с пачками с измененными параметрами)
Сначала установите шаблоны для окончательных аргументов, которые вы хотите использовать. Вам придется явно перечислять их, так как у вас может быть только один пакет параметров.
(Взято из tuple_future_main.cc):
Пример сценария:
Нам нравится добавлять вещи в контейнеры с именем, что-то вроде формы:
add_item(const Item& item, const string& name, Container& c)
Мы также можем создать элемент с [ужасно большим] числом перегрузок и
у нас есть перегрузки:
add_item(${ITEM_CTOR_ARGS}, const string& name, Container& c)
Чтобы сделать это, мы можем объявить следующее:
void add_item_direct(const Item& item, const string& name, Container& c)
Item create_item(Args&&... args)
И затем определите наши общие интерфейсы:
template<typename... Args>
void add_item(Args&&... args) {
...
auto reversed = stdcustom::make_callable_reversed(callable);
reversed(std::forward<Args>(args)...);
}
template<typename ... RevArgs>
void add_item_reversed(Container& c, const string& name, RevArgs&&... revargs)
{
...
static auto ctor = VARIADIC_CALLABLE(create_item,);
...
auto item = ctor_reversed(std::forward<RevArgs>(revargs)...);
add_item_direct(item, name, c);
}
Теперь мы можем делать такие вещи, как: (взято из tuple_future_main.output.txt
)
>>> (add_item(Item("attribute", 12), "bob", c));
>>> (add_item("attribute", 12, "bob", c));
>>> (add_item(Item(2, 2.5, "twelve"), "george", c));
>>> (add_item(2, 2.5, "twelve", "george", c));
>>> (add_item(Item(2, 15.), "again", c));
>>> (add_item(2, 15., "again", c));
>>> c
bob - ctor3: ctor3: ctor1: attribute (12, 10)
bob - ctor3: ctor1: attribute (12, 10)
george - ctor3: ctor3: ctor2: 2, 2.5 (twelve)
george - ctor3: ctor2: 2, 2.5 (twelve)
again - ctor3: ctor3: ctor2: 2, 15 ()
again - ctor3: ctor2: 2, 15 ()
Обратите внимание на дополнительные конструкторы копирования...: (
Недостатки
- Уродливый как ад
- Не может быть полезным
- Вам может быть проще просто реорганизовать ваши интерфейсы
- Однако это можно использовать в качестве стоп-зазора для перехода к более обобщенному интерфейсу.
- Возможно, будет меньше строк для удаления.
- Особенно, если он подключает ваш процесс разработки с использованием шаблонных взрывов.
- Невозможно пригводиться, откуда поступают дополнительные копии.
- Возможно, это связано с разумным использованием вариативных лямбда
- Вы должны тщательно использовать свою базовую функциональность
- Вам не следует пытаться расширить существующую функцию.
- Пакеты параметров будут жадными в том, как они соответствуют функциям
- Вам либо нужно явно указать каждую перегрузку, которую вы хотите, либо поклониться и позволить пакетному пакету параметров посылать требуемую функциональность
- Если вы найдете элегантный способ обойти это, пожалуйста, дайте мне знать.
- Ошибки шаблонов - дерьмовые.
- Конечно, не слишком хреново. Но трудно сделать вывод, что вы пропустили доступную перегрузку.
- Обтекает множество простых функций в лямбдах
- Вы можете использовать
make_reversed_index_sequence
и напрямую отправлять функции (упомянутые в других сообщениях SO). Но это больно повторять.
Todo
Надеется
- Возможно, С++ 17 будет поддерживать неопределенные аргументы пакета параметров, так что все это можно отбросить... скрещенные пальцы
Ответ 4
Из рабочего проекта стандарта N3376 § 14.1 это вероятный раздел, чтобы прочитать об этом.
Ниже приведено § 14.1.11
Если шаблонный шаблон шаблона класса или шаблон псевдонима имеет шаблон-аргумент по умолчанию, каждый последующий шаблон-параметр должен либо иметь заданный по умолчанию шаблон-аргумент, либо быть шаблоном пакет параметров. Если шаблон-параметр шаблона первичного класса или alias template является пакетом параметров шаблона, он должен быть последним Шаблон-параметры. Пакет параметров шаблона функции не должен следовать другой параметр шаблона, за исключением тех случаев, когда параметр шаблона можно вывести из списка параметров-типа или имеет аргумент по умолчанию.