Шаблон функции 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: Обратные аргументы

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

  • tuple.cc - Игровая площадка для двух альтернатив (кредиты в исходном коде):
    • Используйте складные выражения и манипулируйте индексом, переданным через std::apply_impl (кредит: Orient).
    • Используйте рекурсивные шаблоны для создания обратного index_sequence (credit: 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 - Пример вывода

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

  • Избавьтесь от дополнительных копий.
  • Свести к минимуму потребность в всех лямбдах
    • Не нужно, если у вас есть 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
      

Надеется

  • Возможно, С++ 17 будет поддерживать неопределенные аргументы пакета параметров, так что все это можно отбросить... скрещенные пальцы

Ответ 4

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

Ниже приведено § 14.1.11

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