Пакеты параметров шаблона доступа N-го типа и N-го элемента
Следующий документ - первое предложение, которое я нашел для пакетов параметров шаблона.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1603.pdf
На стр. 16 говорится о введении двух новых операторов [] и < > для доступа к элементам пакета параметров и типам пакетов параметров.
The suggested syntax for such an operator involves two new operators: .[] to access values and .<> to access types. For instance:
template<int N, typename Tuple> struct tuple_element;
template<int N, ... Elements>
struct tuple_element<tuple<Elements...> >
{
typedef Elements.<N> type;
};
template<int N, ... Elements>
Elements.<N>& get(tuple<Elements...>& t)
{ return t.[N]; }
template<int N, ... Elements>
const Elements.<N>& get(const tuple<Elements...>& t)
{ return t.[N]; }
Итак, где же эти операторы? Если их нет, какова их замена?
Ответы
Ответ 1
С++ 11 не имеет соответствующих операторов, поэтому они и предлагаются. С С++ 11 вам нужно либо извлечь соответствующую информацию самостоятельно, либо использовать класс, который уже выполняет необходимую операцию. Самый простой подход - это просто использовать std::tuple<T...>
, который уже реализует соответствующую логику.
Если вам интересно, как std::tuple<T...>
в настоящее время реализует эти операции: это в основном упражнение в функциональном программировании с использованием довольно плохой функциональной нотации программирования. Как только вы узнаете, как получить n
-ный тип последовательности, получение элемента n
-th с использованием наследования базовых классов, параметризованных по индексу и типу, довольно тривиально. Реализация чего-то типа tuple_element<N, T...>
может выглядеть примерно так:
template <int N, typename... T>
struct tuple_element;
template <typename T0, typename... T>
struct tuple_element<0, T0, T...> {
typedef T0 type;
};
template <int N, typename T0, typename... T>
struct tuple_element<N, T0, T...> {
typedef typename tuple_element<N-1, T...>::type type;
};
Фактически более сложный бит в реализации чего-то вроде std::tuple<T...>
вызывает набор индексов, поэтому вы получаете параллельный список типов и целых чисел, которые затем могут быть расширены, например, для списка базовых классов, используя что-то вроде ( как внутренние детали выглядят точно, будет отличаться, но основная идея наличия пакетов параллельных параметров для типов и их индексов будет как-то там):
template <typename... T, int... I>
class tuple_base<tuple_types<T...>, tuple_indices<I...>>:
public tuple_field<T, I>... {
};
Ответ 2
Другие уже ответили, что это можно сделать через std::tuple
. Если вы хотите получить доступ к набору параметров N-го типа, вы можете найти следующую метафункцию:
template<int N, typename... Ts> using NthTypeOf =
typename std::tuple_element<N, std::tuple<Ts...>>::type;
Использование:
using ThirdType = NthTypeOf<2, Ts...>;
Ответ 3
Чтобы получить N-й элемент из пакета, вы можете написать:
Вариант 1
Использование tuple_element для получения типа возврата для элемента Nth:
template<size_t index, typename T, typename... Ts>
inline constexpr typename enable_if<index==0, T>::type
get(T&& t, Ts&&... ts) {
return t;
}
template<size_t index, typename T, typename... Ts>
inline constexpr typename enable_if<(index > 0) && index <= sizeof...(Ts),
typename tuple_element<index, tuple<T, Ts...>>::type>::type
get(T&& t, Ts&&... ts) {
return get<index-1>(std::forward<Ts>(ts)...);
}
// below is optional - just for getting a more readable compilation error
// in case calling get with a bad index
inline template<long long index, typename... Ts>
constexpr bool index_ok() {
return index >= 0 && index < sizeof...(Ts);
}
template<long long index, typename T, typename... Ts>
inline constexpr
typename enable_if<!index_ok<index, T, Ts...>(), T>::type
get(T&& t, Ts&&... ts) {
static_assert(index_ok<index, T, Ts...>(),
"bad index in call to get, smaller than zero or above pack size");
return t;
}
Вариант 2
Без использования кортежа, полагаясь на тип автоматического возврата и, в частности, на С++ 14 decltype (auto) и при использовании enable_if в качестве параметра шаблона, а не как возвращаемый тип:
template<size_t index, typename T, typename... Ts,
typename enable_if<index==0>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
return std::forward<T>(t);
}
template<size_t index, typename T, typename... Ts,
typename enable_if<(index > 0 && index <= sizeof...(Ts))>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
return get<index-1>(std::forward<Ts>(ts)...);
}
template<long long index, typename... Ts>
inline constexpr bool index_ok() {
return index >= 0 && index < (long long)sizeof...(Ts);
}
// block (compilation error) the call to get with bad index,
// providing a readable compilation error
template<long long index, typename T, typename... Ts,
typename enable_if<(!index_ok<index, T, Ts...>())>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
static_assert(index_ok<index, T, Ts...>(),
"bad index in call to get, smaller than zero or above pack size");
return std::forward<T>(t); // need to return something...
// we hope to fail on the static_assert above
}
Пример использования:
template<size_t index, typename... Ts>
void resetElementN(Ts&&... ts) {
get<index>(std::forward<Ts>(ts)...) = {}; // assuming element N has an empty ctor
}
int main() {
int i = 0;
string s = "hello";
get<0>(i,2,"hello","hello"s, 'a') += get<0>(2);
get<1>(1,i,"hello",4) += get<1>(1, 2);
get<3>(1,2,"hello",i) += get<2>(0, 1, 2);
get<2>(1,2,s,4) = get<2>(0, 1, "hi");
cout << i << ' ' << s << endl;
resetElementN<1>(0, i, 2);
resetElementN<0>(s, 1, 2);
cout << i << ' ' << s << endl;
// not ok - and do not compile
// get<0>(1,i,"hello","hello"s) = 5;
// get<1>(1,i*2,"hello") = 5;
// get<2>(1,i*2,"hello")[4] = '!';
// resetElementN<1>(s, 1, 2);
// ok
const int j = 2;
cout << get<0>(j,i,3,4) << endl;
// not ok - and do not compile
// get<0>(j,i,3,4) = 5;
// not ok - and do not compile
// with a readable compilation error
// cout << get<-1>("one", 2, '3') << endl;
// cout << get<3>("one", 2, '3') << endl;
}
Код
Вариант 1: http://coliru.stacked-crooked.com/a/60ad3d860aa94453
Вариант 2: http://coliru.stacked-crooked.com/a/09f6e8e155612f8b
Ответ 4
Мы можем реализовать простую функцию, чтобы получить n-й параметр непосредственно без каких-либо рекурсивных вызовов, но многие операции чистого типа во время компиляции.
Сначала рассмотрим key code:
template<class...Ts>
struct GetImp {
template<class T, class...Us>
static decltype(auto) impl(Ts&&..., T&& obj, Us&&...) {
return std::forward<T>(obj);
}
};
template<size_t n, class...Ts>
decltype(auto) get(Ts&&...args) {
static_assert(n<sizeof...(args), "index over range");
return Transform<GetImp, Before_s<n, Seq<Ts...>> >
::impl(std::forward<Ts>(args)...);
}
Что означает Transform?
Например, если у нас есть тип T
, который std::tuple<int,double,float>
,
то Transform<GetImp,T>
будет GetImp<int,double,float>
.
обратите внимание, что я определяю еще одну пустую структуру "Seq" вместо std::tuple
, чтобы сделать
то же самое с меньшим временем компиляции. (Фактически, оба они могут быть скомпилированы очень быстро, но я думаю, что пустая структура будет более эффективно)
Итак, Before_s<n,Seq<Ts...>>
сгенерируем Seq<?>
, а затем преобразуем его в GetImp, чтобы мы могли узнать, какие типы параметров [0] ~ [n-1]
а затем отбросить их, чтобы напрямую индексировать n-й параметр.
Например, Before_s<3,Seq<T0,T1,T2,T3,T4...>>
- Seq<T0,T1,T2>
,
Before_s<2,Seq<T0,T1,T2,T3,T4...>>
- Seq<T0,T1>
и т.д.
Мы используем Before_s для работы с нашим типом Seq, чтобы сократить время компиляции, когда мы
использовать одну мета-функцию для реализации другой мета-функции для менее компиляции
время.
Реализация
#define OMIT_T(...) typename __VA_ARGS__::type
template<class...Args>
struct Seq { };
template< template<class...> class Dst >
struct TransformImp{
template< template<class...>class Src, class...Args >
static Dst<Args...> from(Src<Args...>&&);
};
template< template<class...> class Dst, class T>
using Transform = decltype(TransformImp<Dst>::from(std::declval<T>()));
template<class T>
using Seqfy = Transform<Seq, T>;
template<class...>struct MergeImp;
template<class...Ts, class...Others>
struct MergeImp<Seq<Ts...>, Seq<Others...>>
{
using type = Seq<Ts..., Others...>;
};
template<class first, class second>
using Merge = OMIT_T(MergeImp<Seqfy<first>, Seqfy<second> >);
template<class T, class U>
using Merge_s = OMIT_T(MergeImp<T, U>);
template<size_t, class...>struct BeforeImp;
template<size_t n, class T, class...Ts>
struct BeforeImp<n, Seq<T, Ts...>> {
static_assert(n <= sizeof...(Ts)+1, "index over range");
using type = Merge_s<Seq<T>, OMIT_T(BeforeImp<n - 1, Seq<Ts...>>)>;
};
template<class T, class...Ts>
struct BeforeImp<1, Seq<T, Ts...>> {
using type = Seq<T>;
};
template<class T, class...Ts>
struct BeforeImp<0, Seq<T, Ts...>> {
using type = Seq<>;
};
template<size_t n>
struct BeforeImp<n, Seq<>> {
using type = Seq<>;
};
template<size_t n, class T>
using Before = OMIT_T(BeforeImp<n, Seqfy<T>>);
template<size_t n, class T>
using Before_s = OMIT_T(BeforeImp<n, T>);
Отредактировано: Расширенная реализация
Нам не нужно использовать Before_s для вычисления n-1 типов до n-го типа, вместо этого,
мы можем их игнорировать:
struct EatParam{
constexpr EatParam(...)noexcept{}
};
template<size_t n>
struct GenSeqImp {
using type = Merge_s<OMIT_T(GenSeqImp<n / 2>), OMIT_T(GenSeqImp<n - n / 2>)>;
};
template<>
struct GenSeqImp<0> {
using type = Seq<>;
};
template<>
struct GenSeqImp<1> {
using type = Seq<EatParam>;
};
template<size_t n>
using GenSeq = OMIT_T(GenSeqImp<n>);
template<class...Ts>
struct GetImp {
template<class T>
static constexpr decltype(auto) impl(Ts&&..., T&& obj, ...)noexcept {
return std::forward<T>(obj);
}
};
template<size_t n, class...Ts>
constexpr decltype(auto) get(Ts&&...args)noexcept {
static_assert(n<sizeof...(args), "index over range.");
//return Transform<GetImp, Before_s<n, Seq<Ts...>> >
return Transform<GetImp, GenSeq<n>>
::impl(std::forward<Ts>(args)...);
}
Кроме того, там есть очень интересная статья о реализации получения n-го типа:
Спасибо за их работу, я не знал, что мы могли бы использовать (...), чтобы сделать взлом раньше.
Ответ 5
Доступ к N-му элементу?
Использование std::forward_as_tuple
:
template <int I, class... Ts>
decltype(auto) get(Ts&&... ts) {
return std::get<I>(std::forward_as_tuple(ts...));
}
Пример использования:
template<class...Ts>
void foo(Ts&&...ts){
auto& first = get<0>(ts...);
auto second = get<1>(ts...);
first = 'H';
second = 'E';
(std::cout << ... << ts);
}
foo('h','e','l','l','o');
// prints "Hello"
Этот ответ должен дополнять ответ Эмиля Кормье, который дает только n-ый тип.