3
votes

Spécialisation partielle avec des paramètres de modèles variadiques suivis d'autres paramètres

J'ai un problème avec la spécialisation partielle impliquant des paramètres de modèle variadique. La spécialisation avec un préfixe

#include <iostream>

template<char ... C>
struct Str
{
    static constexpr char Value[] = { C..., '\0' };
};

template<char ... C>
constexpr char Str<C...>::Value[];

template<typename>
struct TrimFront;

template<char A, char ... C>
struct TrimFront<Str<A, C...>>
{
    typedef Str<C...> Type;
};

template<typename>
struct TrimBack;

template<char A, char ... C>
struct TrimBack<Str<C..., A>>
{
    typedef Str<C...> Type;
};

int main(int, char **)
{
    typedef Str<'a', 'b', 'c', 'd', 'e', 'f'> str_t;
    std::cout << str_t::Value << std::endl; // prints "abcdef"
    std::cout << TrimFront<str_t>::Type::Value << std::endl; // prints "bcdef"
    std::cout << TrimBack<str_t>::Type::Value << std::endl; // ERROR (incomplete type)
    return 0;
}

fonctionne comme prévu, mais quand j'essaie de faire correspondre avec le suffixe

template<typename A, typename ... B>
struct Foo<B..., A> { };

cela ne fonctionne pas. Y a-t-il une règle que je ne connais pas ou est-ce un problème de compilation? (J'utilise G ++ 7.4, spécifiquement x86_64-w64-mingw32-g ++ de cygwin)

Exemple autonome pour démontrer mon problème:

template<typename A, typename ... B>
struct Foo<A, B...> { };


3 commentaires

le problème est qu'une liste variadique déduite doit être en dernière position; donc A, B ... est ok, A ..., B ne fonctionne pas.


@ max66 vraiment? très malheureux. pouvez-vous par hasard indiquer où cela est dit dans la norme? je n'arrive pas à trouver la partie pertinente.


J'avoue que je ne suis pas juriste en langues; mais j'ai ajouté une réponse en essayant de répondre à votre question. J'espère que cela aide.


3 Réponses :


0
votes

Voici une solution utilisant boost :: mp11 :

Commentaires en ligne:

#include <iostream>
#include <boost/mp11.hpp>

using namespace boost::mp11;

template<char c> 
struct c_char {
    static constexpr char value() { return c; }
};

template<typename...> struct Str;

template<char... C>
struct Str<c_char<C>...>
{
    static constexpr auto size() -> std::size_t { return sizeof...(C) + 1; }
    static constexpr char Value [size()] = { C..., '\0' };
};

template<char...C> using make_Str = Str<c_char<C>...>;

template<typename List>
struct TrimFront
{
    using Type = mp_pop_front<List>;
};

template<typename List>
struct TrimBack
{ 
    using Type = mp_reverse<mp_pop_front<mp_reverse<List>>>;
};

int main(int, char **)
{
    using str_t = make_Str<'a', 'b', 'c', 'd', 'e', 'f'>;
    std::cout << str_t::Value << std::endl; // prints "abcdef"
    std::cout << TrimFront<str_t>::Type::Value << std::endl; // prints "bcdef"
    std::cout << TrimBack<str_t>::Type::Value << std::endl; // prints "abcde"
    return 0;
}

Résultat attendu:

abcdef
bcdef
abcde

http://coliru.stacked-crooked.com/a/ 387e5dc7ef262f1f

Grâce à nos nouvelles connaissances, nous pouvons simplifier:

#include <iostream>
#include <boost/mp11.hpp>

template<char ... C>
struct Str
{
    static constexpr char Value[] = { C..., '\0' };
};

template<char ... C>
constexpr char Str<C...>::Value[];

template<typename>
struct TrimFront;

template<char A, char ... C>
struct TrimFront<Str<A, C...>>
{
    typedef Str<C...> Type;
};

template<typename>
struct TrimBack;



using namespace boost::mp11;

// a means of turning chars into types
template<char c> struct c_char {
    constexpr char value() { return c; }
 };

// a means of turning an mp_list of c_char<char>... back into a Str<char...>
 template<typename>
 struct back_to_Str;

 template<char...cs>
 struct back_to_Str<mp_list<c_char<cs>...>>
 {
     using result = Str<cs...>;
 };

// TrimBack using types as computation steps:
template<char... C>
struct TrimBack<Str<C...>>
{ 
    // turn the input chars into an mp_list of c_char
    // always use types, they're much easier than values when metaprogramming
    using input = mp_list<c_char<C>...>;        

    // reverse the list
    using reversed = mp_reverse<input>;

    // pop the front c_char<>
    using popped = mp_pop_front<reversed>;

    // reverse again
    using re_reversed = mp_reverse<popped>;

    // turn back into a Str<char...>
    using Type = typename back_to_Str<re_reversed>::result;
};

int main(int, char **)
{
    typedef Str<'a', 'b', 'c', 'd', 'e', 'f'> str_t;
    std::cout << str_t::Value << std::endl; // prints "abcdef"
    std::cout << TrimFront<str_t>::Type::Value << std::endl; // prints "bcdef"
    std::cout << TrimBack<str_t>::Type::Value << std::endl; // prints "abcde"
    return 0;
}


2 commentaires

merci pour votre réponse détaillée, mais j'ai bien peur que cela ne réponde pas à ma question. je sais comment résoudre le problème que j'ai donné (modèle variadic -> type list -> opération de manipulation de liste arbitraire -> modèle variadic), mais ce n'était qu'un exemple pour clarifier ma question: puis-je me spécialiser partiellement avec un pack d'arguments variadiques ce n'est pas à la fin: my_struct ? j'essaye d'éviter la solution de type-list parce que dans le cas d'utilisation réel, le modèle variadic contient plusieurs centaines de types; reverse -> pop -> reverse aurait tout à fait la pénalité de performance


@DaveDanielKessener Je vois. Je me demande s'il y a quelque chose qui pourrait être fait avec index_sequence et tuples.



1
votes

Je suppose que

template<char A, char ... C>
struct TrimBack<Str<C..., A>>
{
    typedef Str<C...> Type;
};

ne peut pas fonctionner (" A " et " C ... ") ne peut pas être déduit car le pack variadique ( C ... ) n'est pas en dernière position.

L'OP, raisonnablement, demande une référence

vraiment? très malheureux. pouvez-vous par hasard indiquer où cela est dit dans la norme? je n'arrive pas à trouver la partie pertinente

Je ne suis pas une couche de langage mais il me semble que la partie pertinente (standard C ++ 11) est 14.8.2.5 ("Déduire l'argument modèle d'un type", "[temp.deduct.type] "), point 9 (c'est moi qui souligne)

Si P a une forme qui contient ou , alors chaque argument P_i de la liste d'arguments de modèle respective P est comparée à l'argument correspondant A_i de la liste d'arguments de modèle correspondante de A . Si la liste d'arguments de modèle de P contient une extension de pack qui n'est pas le dernier argument de modèle, toute la liste d'arguments de modèle est un contexte non déduit . Si P_i est une extension de pack, alors le modèle de P_i est comparé à chaque argument restant dans la liste d'arguments de modèle de A . Chaque comparaison déduit des arguments de modèle pour les positions suivantes dans les packs de paramètres de modèle développés par P_i .

Donc, si je ne me trompe pas, TrimBack (aka TrimBack > ) donne une erreur car

1) dans une première phase, Str correspond Str

2) mais dans une deuxième phase, en essayant de déduire les types C ... et A , P (c'est-à-dire Str , dans cette phase) "contient une extension de pack qui n'est pas le dernier argument du modèle", donc "la liste complète des arguments du modèle est un contexte non déduit".


0 commentaires

1
votes

Une spécialisation partielle de modèle de classe comme celle-ci

std::cout << TrimBack<str_t>::Type::Value << std::endl;  // prints "abcde"

n'est pas autorisée, car pour déduire C ... et A , d'un type est effectuée, et un argument pack qui n'est pas le dernier en fait un contexte non déduit .

Ce que vous pouvez faire à la place, c'est utiliser un type d'aide pour "déballer" le pack, puis le "reconditionner", moins le dernier élément .

template <char ...P>
struct dummy {};

template <class T, char ...P>
struct internal;

template <char ...P1, char T, char ...P2>
struct internal<dummy<P1...>, T, P2...>
{
    using type = typename internal<dummy<P1..., T>, P2...>::type; // unwrap one recursively
};

template <char ...P1, char T>
struct internal<dummy<P1...>, T>
{
    using type = Str<P1...>; // re-wrap all but the last one
};

template <typename>
struct TrimBack;

template <char ...C>
struct TrimBack<Str<C...>>
{
    using Type = typename internal<dummy<>, C...>::type;
};

Cela devrait maintenant fonctionner:

template<typename> struct TrimBack;
template<char ...C, char A> struct TrimBack<Str<C..., A>> {}

Démo en direct


4 commentaires

J'avoue que je suis confus par la norme mais ... êtes-vous sûr que (8.5) de 14.5.5 s'applique dans ce cas? Je veux dire ... dans ce cas, "argument" est " Str " ou " C ... "?


J'avais peur qu'il ne soit pas légal de faire correspondre un pack de paramètres de modèle variadique qui n'est pas à la fin. merci pour la solution proposée, mais comme la réponse de Richard Hodges, une approche avec une complexité O (N) n'est pas idéale.


Je crains qu'il n'y ait malheureusement pas de solution O (1), au mieux vous pouvez le rendre O (logN) avec un TMP intelligent, mais ça va être beaucoup moins lisible. N'oubliez pas que nous ne parlons que du travail de compilation. @ max66 Je pense que vous avez raison, j'ai mis à jour ma réponse.


Je suis d'accord avec la première référence (14.8.2.5 point 1, "déduction d'un type") mais la seconde (point 5.6, "contect non déduit") concerne les fonctions