Comment créer une fonction d’indexation chaînée basée sur les variadés?

Si j’ai un object qui soit un tableau intégré ou un type de classe avec un operator [] approprié operator [] , et son type retourné peut être indexé lui-même, comment devrais-je écrire une fonction générique qui peut tous les indexer avec une variable appeler au lieu de bloc de support séparé? En d’autres termes, je peux faire des expressions comme:

 a[i0][i1]...[iK] 

et je veux pouvoir l’écrire comme une seule fonction:

 slice( a, i0, i1, ..., iK ) 

puisque les règles de C ++ exigent que l’ operator [] travaille sur des arguments simples, ce qui le rend moins compatible avec les éléments variadiques. (Cette question est basée sur un fil Usenet dans lequel j’ai essayé de poser une question similaire; je n’ai finalement résolu que moi-même les tableaux intégrés éventuellement nesteds.)

Un premier coup de poignard:

 template  constexpr auto slice( T &&t, U &&u ) noexcept(???) -> ??? { return ce_forward(t)[ ce_forward(u) ]; } template  constexpr auto slice( T &&t, U &&u, V &&v, W... &&w ) noexcept(???) -> ??? { return slice( ce_forward(t)[ce_forward(u)], ce_forward(v), ce_forward(w)... ); } 

Les ce_forward fonction ce_forward sont std::forward constmarkés. (Certains éléments de la norme qui pourraient être marqués constexpr ne le sont pas.) J’essaie de trouver le bon matériel à insérer dans les zones de type de retour et de spécification d’exception. Certains cas et mises en garde que je connais sont les suivants:

  • L’ operator [] intégré operator [] nécessite qu’un opérande soit un pointeur de données (ou une référence de tableau décomposé) et l’autre un type énumération ou entier. Les opérandes peuvent être dans l’un ou l’autre ordre. Je ne sais pas si un opérande pourrait être remplacé par un type de classe avec une conversion non ambiguë (non explicite?) En un type approprié.
  • Un type de classe peut définir l’ operator [] tant que fonction (s) membre non statique. Une telle fonction ne peut avoir qu’un seul paramètre (à part this ).
  • L’opérateur intégré ne peut pas lancer. (À moins que les opérandes ne puissent être basés sur une conversion UDT; cette conversion est le seul sharepoint projection possible.) Le comportement indéfini causé par des erreurs de limites est au-delà de notre compétence ici.
  • Que le dernier appel d’indexation retourne une référence de valeur l, une référence de valeur r ou une valeur doit être reflété dans le type de retour de la slice .
  • Si l’appel (final?) Est une valeur, alors std::is_nothrow_move_constructible::value doit être OUI pour la spécification de l’exception. (Les retours par référence sont noexcept .)
  • Si l’opérateur intégré implique un tableau, la référence renvoyée pour cette étape doit être une référence à la valeur r si le tableau en contient un également. (C’est un peu un défaut car les pointeurs, contrairement aux tableaux, perdent le statut valeur de l- contre r de leur cible, de sorte que le retour traditionnel serait toujours une référence de valeur-l.)
  • Pour les opérateurs d’indexation de type classe, assurez-vous que les sections exception-spec et type-retour font référence à la même surcharge (s’il en existe plusieurs) utilisée par le corps de la fonction. Méfiez-vous des surcharges qui ne diffèrent que par la qualification de this ( const / volatile / both / ni et / ou & / & && / ni).

J’ai mis au point une solution basée sur le commentaire de @ luc-danton et sur la réponse de @ user315052.

 #include  template < typename Base, typename ...Indices > class indexing_result; template < typename T > class indexing_result { public: using type = T; static constexpr bool can_throw = false; }; template < typename T, typename U, typename ...V > class indexing_result { using direct_type = decltype( std::declval()[std::declval()] ); using next_type = indexing_result; static constexpr bool direct_can_throw = not noexcept( std::declval()[std::declval()] ); public: using type = typename next_type::type; static constexpr bool can_throw = direct_can_throw || next_type::can_throw; }; template < typename T > inline constexpr auto slice( T &&t ) noexcept -> T && { return static_cast(t); } template < typename T, typename U, typename ...V > inline constexpr auto slice( T &&t, U &&u, V &&...v ) noexcept( !indexing_result::can_throw ) -> typename indexing_result::type { return slice( static_cast(t)[static_cast( u )], static_cast(v)... ); } 

Je mets un exemple complet de programme en tant que Gist. Cela fonctionnait sur un compilateur de site Web exécutant GCC> = 4.7, CLang> = 3.2 et Intel C ++> = 13.0.

Pour obtenir la bonne valeur de retour pour la fonction slice , vous pouvez créer un modèle d’assistance qui calcule le type de résultat correct. Par exemple:

 template  struct SliceResult { typedef typename SliceResult::Type Type; }; template  struct SliceResult { typedef typename std::underlying_type::type Type; }; 

La fonction slice renverrait alors un SliceResult<...>::Type .