Мне интересно, почему следующий код не компилируется:
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.
Если стандарты запрещают делать такие вещи, кто-нибудь знает почему?
Поскольку, когда пакет параметров функции не является последним параметром, пакет параметров шаблона не может быть выведен из него, и он будет игнорироваться при выводе аргумента шаблона.
Итак, два аргумента 0, 0
сравниваются с , int
приводя к несоответствию.
Подобные правила вычитания должны охватывать множество особых случаев (например, что происходит, когда два пакета параметров появляются рядом друг с другом). Поскольку пакеты параметров являются новой функцией в C ++ 11, авторы соответствующего предложения разработали правила консервативно.
Обратите внимание, что пакет параметров конечного шаблона будет пустым, если он не выведен другим способом. Поэтому, когда вы вызываете конструктор с одним аргументом, все будет работать (обратите внимание на разницу между пакетом параметров шаблона и пакетом параметров функции. Первый — завершающий, а второй — нет).
Итак, должен быть обходной путь. Что-то вроде этого:
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
,
Я на самом деле немного заинтересован в том же (хочу специализировать шаблоны пакетов на основе окончательных аргументов).
Я считаю, что может быть путь вперед, комбинируя обращение кортежей (std::make_tuple
, обратный порт std::apply
для C ++ 14 и т. д.):
Вернемся сюда, если это удастся.
Похожие сообщения:
РЕДАКТИРОВАТЬ:
Да, понял это через некоторое время; не идеально, так как есть дополнительные копии, летающие вокруг, но это начало.
Если вы знаете более простой способ, чем то, что я перечислил ниже, пожалуйста, не стесняйтесь писать!
Можно сделать что-то вроде этого:
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).
Есть несколько разных способов сделать это. Я перечислил некоторые альтернативы в этом примере:
std::apply_impl
(кредит: Ориент).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, так как мне легче переваривать.
Затем я попытался быстро оформить это:
namespace stdfuture
) и сделать расширение (namespace stdcustom
)Определение фрагментов (адаптация 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);
Сначала установите шаблоны для окончательных аргументов, которые вы хотите использовать. Вам придется явно перечислить их, так как вы можете иметь только один пакет параметров.
(Взято из 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). Но это больно повторять.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
Из рабочего проекта стандарта N3376 § 14.1 есть вероятный раздел, чтобы прочитать об этом.
Ниже § 14.1.11
Если параметр-шаблона шаблона класса или псевдонима имеет
шаблонный аргумент по умолчанию, каждый последующий шаблонный параметр должен
либо предоставлен шаблонный аргумент по умолчанию, либо используется шаблон
пакет параметров. Если шаблон-параметр шаблона первичного класса или
alias template — это пакет параметров шаблона, он должен быть последним
Шаблон-параметры. Пакет параметров шаблона шаблона функции
не должен сопровождаться другим параметром шаблона, если это не
Параметр шаблона может быть выведен из списка параметров типа
шаблон функции или имеет аргумент по умолчанию.