web-dev-qa-db-fra.com

les packs de paramètres de modèle accèdent au Nième type et au Nième élément

L'article suivant est la première proposition que j'ai trouvée pour les packs de paramètres de modèle.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1603.pdf

À la page 16, il parle de l'introduction de deux nouveaux opérateurs [] et <> pour accéder aux éléments du pack de paramètres et aux types de packs de paramètres.

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]; }

Où sont donc ces opérateurs? S'il n'y en a pas, quel est leur remplacement?

36
nurettin

C++ 11 n'a pas d'opérateurs correspondants, c'est pourquoi ils sont proposés. Avec C++ 11, vous devrez soit extraire vous-même les informations correspondantes, soit utiliser une classe qui effectue déjà l'opération nécessaire. L'approche la plus simple consiste probablement à utiliser simplement std::Tuple<T...> qui implémente déjà la logique correspondante.

Si vous vous demandez comment std::Tuple<T...> implémente actuellement ces opérations: il s'agit essentiellement d'un exercice de programmation fonctionnelle utilisant une notation de programmation fonctionnelle assez mauvaise. Une fois que vous savez comment obtenir le type n- ème de la séquence, obtenir l'élément n- th en utilisant l'héritage des classes de base paramétrées sur l'index et le type est assez trivial. Implémenter quelque chose comme Tuple_element<N, T...> pourrait ressembler à ceci:

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;
};

La partie la plus difficile de la mise en œuvre de quelque chose comme std::Tuple<T...> crée une liste d'indices, vous avez donc une liste parallèle de types et d'entiers qui peut ensuite être développée, par exemple, pour une liste de classes de base en utilisant quelque chose comme (à quoi ressembleront exactement les détails internes, mais l'idée de base de avoir un ensemble de paramètres parallèles pour les types et leurs indices sera en quelque sorte là):

template <typename... T, int... I>
class Tuple_base<Tuple_types<T...>, Tuple_indices<I...>>:
     public Tuple_field<T, I>... {
};
23
Dietmar Kühl

D'autres ont déjà répondu que cela peut être fait via std::Tuple. Si vous souhaitez accéder au Nème type d'un pack de paramètres, vous pouvez trouver la métafonction suivante à portée de main:

template<int N, typename... Ts> using NthTypeOf =
        typename std::Tuple_element<N, std::Tuple<Ts...>>::type;

Usage:

using ThirdType = NthTypeOf<2, Ts...>;
43
Emile Cormier

Accéder au Nième élément?

En utilisant std::forward_as_Tuple:

template <int I, class... Ts>
decltype(auto) get(Ts&&... ts) {
  return std::get<I>(std::forward_as_Tuple(ts...));
}

Exemple d'utilisation:

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"

Cette réponse vient compléter la réponse d'Emile Cormier qui ne donne que le nième type.

7
tom

Pour obtenir le Nième élément d'un pack, vous pouvez écrire:

Option 1

Utilisation de Tuple_element pour obtenir le type de retour pour le Nème élément:

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;
}

Option 2

Sans utiliser Tuple, en s'appuyant sur type de retour automatique et spécifiquement sur C++ 14 decltype (auto) et sur l'utilisation enable_if comme paramètre de modèle et non comme type de retour:

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
}

Exemple d'utilisation:

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;
}

Code
Option 1: http://coliru.stacked-crooked.com/a/60ad3d860aa9445
Option 2: http://coliru.stacked-crooked.com/a/09f6e8e155612f8b

5
Amir Kirsh

Nous pouvons implémenter une fonction simple pour obtenir directement le nième paramètre sans aucun appel récursif mais de nombreuses opérations de type pur en compilation. Regardons d'abord le code clé:

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

Que signifie Transformer?

Par exemple, si nous avons un type T qui est std::Tuple<int,double,float>, puis Transform<GetImp,T> serait GetImp<int,double,float>. notez que je définis une autre structure vide "Seq" au lieu de std::Tuple pour faire la même chose avec moins de temps de compilation. (En fait, les deux pourraient être compilés très rapidement, mais je suppose qu'une structure vide serait plus efficace) Donc Before_s<n,Seq<Ts...>> générer un Seq<?> puis nous le transformons en GetImp, afin que nous puissions savoir quel type de paramètres [0] ~ [n-1] sont, puis les déposer pour indexer directement le nième paramètre. Par exemple, Before_s<3,Seq<T0,T1,T2,T3,T4...>> est Seq<T0,T1,T2>, Before_s<2,Seq<T0,T1,T2,T3,T4...>> est Seq<T0,T1> etc. Nous utilisons Before_s pour gérer notre type Seq afin de réduire le temps de compilation lorsque nous utilisons une méta-fonction pour implémenter une autre méta-fonction pour moins de temps de compilation.

Implémentation

#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>);

Modifié: implémentation avancée

Nous n'avons pas besoin d'utiliser Before_s pour calculer les n-1 types avant le nième type, nous pouvons plutôt les ignorer:

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

De plus, there est un article très intéressant sur l'implémentation de l'obtention du nième type:

Merci pour leur travail, je ne savais pas que nous pouvions utiliser (...) pour faire le hack avant.

2
Landersing