Шаблон функции 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.

Если стандарты запрещают делать такие вещи, кто-нибудь знает почему?

13

Решение

Поскольку, когда пакет параметров функции не является последним параметром, пакет параметров шаблона не может быть выведен из него, и он будет игнорироваться при выводе аргумента шаблона.

Итак, два аргумента 0, 0 сравниваются с , intприводя к несоответствию.

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

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

9

Другие решения

Итак, должен быть обходной путь. Что-то вроде этого:

namespace v1 {
// 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;
};
}
namespace v2 {
template<class T> struct tag_t{using type=T;};
template<class T> using type_t = typename T::type;
template<class...Ts>
using last = type_t< std::tuple_element_t< sizeof...(Ts)-1, std::tuple<tag_t<Ts>...> > >;
template<class...Ts>
struct last_type {
using type=last<Ts...>;
};
}
template<class...Ts>
using last_type=v2::late_type<Ts...>; // or v1struct 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или что мы только где прошли int,

6

Я на самом деле немного заинтересован в том же (хочу специализировать шаблоны пакетов на основе окончательных аргументов).

Я считаю, что может быть путь вперед, комбинируя обращение кортежей (std::make_tuple, обратный порт std::apply для C ++ 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);

А затем реализовать этот псевдо-код:

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...)

Используя вышеупомянутые утилиты.

Имеет (много) недостатков. Перечислю их ниже.

Объем

Это для пользователей C ++ 14 (возможно, C ++ 11), которые хотят позаимствовать из будущего (C ++ 17).

Шаг 1: Обратные аргументы

Есть несколько разных способов сделать это. Я перечислил некоторые альтернативы в этом примере:

  • tuple.cc — Площадка для двух вариантов (титры в исходном коде):
    1. Используйте складываемые выражения и манипулируйте индексом, переданным через std::apply_impl (кредит: Ориент).
    2. Используйте рекурсивные шаблоны для построения обращенного index_sequence (кредит: Xeo)
  • tuple.output.txt — Пример вывода

    • Это распечатывает reversed_index_sequence шаблон из примера Xeo. Мне нужно это для отладки.

      >>> name_trait<std::make_index_sequence<5>>::name()
      std::index_sequence<0, 1, 2, 3, 4>
      >>> name_trait<make_reversed_index_sequence<5>>::name()
      std::index_sequence<4, 3, 2, 1, 0>
      

Я выбрал Альтернативу 1, так как мне легче переваривать.
Затем я попытался быстро оформить это:

  • tuple_future.h — Заимствование из будущего (namespace stdfuture) и сделать расширение (namespace stdcustom)
  • tuple_future_main.cc — Простые, сложные и полезные (см. Ниже) примеры, использующие выше
  • tuple_future_main.output.txt — Пример вывода

Определение фрагментов (адаптация C ++ 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 ()

Обратите внимание на дополнительные конструкторы копирования … 🙁

Недостатки

  • Гадкий как ад
  • Не может быть полезным
    • Это может быть проще просто рефакторинг ваших интерфейсов
      • Тем не менее, это может быть использовано в качестве остановки для перехода к более обобщенному интерфейсу.
      • Возможно меньше строк для удаления.
    • Особенно, если он затягивает ваш процесс разработки с помощью шаблонов.
  • Не могу понять, откуда берутся дополнительные копии.
    • Это может быть связано с разумным использованием лямбд
  • Вы должны тщательно обработать свою базовую функциональность
    • Вы не должны пытаться расширить существующую функцию.
    • Пакеты параметров будут жадными в том, как они соответствуют функциям
    • Вам либо нужно явно прописать каждую перегрузку, которую вы хотите, либо поклониться и позволить пакету параметров variadic отправиться к желаемой функциональности
      • Если вы найдете элегантный способ обойти это, пожалуйста, дайте мне знать.
  • Ошибки шаблона дерьмовые.
    • Конечно, не слишком дерьмовый. Но трудно понять, что вы пропустили доступную перегрузку.
  • Оборачивает много простых функций в лямбдах
    • Вы можете быть в состоянии использовать make_reversed_index_sequence и непосредственно отправлять в функцию (упоминается в других сообщениях SO). Но это больно повторять.

Сделать

  • Избавьтесь от лишних копий
  • Минимизировать потребность во всех лямбдах
    • Не обязательно, если у вас есть Callable
  • Попробуйте бороться с жадностью пакета параметров

    • Есть ли обобщенный std::enable_if соответствие, совпадающее как с lvalue-, так и с rvalue-ссылками, и, возможно, обработать пересылку совместимых неявных конструкторов копирования?

      template<typename ... Args>
      void my_func(Args&& ... args) // Greedy
      void my_func(magical_ref_match<string>::type, ...)
      // If this could somehow automatically snatch `const T&` and `T&&` from the parameter pack...
      // And if it can be used flexible with multiple arguments, combinatorically
      

Надеется

  • Может быть, C ++ 17 будет поддерживать не окончательные аргументы пакета параметров, так что все это может быть отброшено … скрещенные пальцы
1

Из рабочего проекта стандарта N3376 § 14.1 есть вероятный раздел, чтобы прочитать об этом.

Ниже § 14.1.11

Если параметр-шаблона шаблона класса или псевдонима имеет
шаблонный аргумент по умолчанию, каждый последующий шаблонный параметр должен
либо предоставлен шаблонный аргумент по умолчанию, либо используется шаблон
пакет параметров. Если шаблон-параметр шаблона первичного класса или
alias template — это пакет параметров шаблона, он должен быть последним
Шаблон-параметры. Пакет параметров шаблона шаблона функции
не должен сопровождаться другим параметром шаблона, если это не
Параметр шаблона может быть выведен из списка параметров типа
шаблон функции или имеет аргумент по умолчанию.

0
По вопросам рекламы [email protected]