2 votes

C++ Multiples paquets de paramètres de modèles dans la spécialisation

Je joue avec des packs de paramètres et j'ai obtenu un comportement que je n'ai pas été en mesure d'expliquer (d'après ce que j'ai compris, il devrait être valide).

J'ai une structure modèle prenant un type et une spécialisation utilisant deux paquets de paramètres ( http://coliru.stacked-crooked.com/a/0cb6c6fef7b09e6f ):

    #include <type_traits>
    template<class, int> struct dummy{};
    template<class...> struct multiple{};

    template<class T, class = std::true_type>
    struct exactly_one_not_0 : std::false_type {};

    template<class...LTs, class...RTs, class T, int V>
    struct exactly_one_not_0
    <
        multiple
        <
            dummy<LTs, 0>...,
            dummy<T, V>,
            dummy<RTs, 0>...
        >,
        std::bool_constant<V>
    > : std::true_type
    {};

    template<class T>
    constexpr bool exactly_one_not_0_v = exactly_one_not_0<T>::value;

    using d0 = dummy<int,0>;
    using d1 = dummy<int,1>;

    static_assert(exactly_one_not_0_v<multiple<d1>>);
    static_assert(exactly_one_not_0_v<multiple<d1,d0>>);
    static_assert(exactly_one_not_0_v<multiple<d0,d1>>);
    static_assert(exactly_one_not_0_v<multiple<d0,d1,d0>>);

    static_assert(!exactly_one_not_0_v<multiple<>>);
    static_assert(!exactly_one_not_0_v<multiple<d0>>);
    static_assert(!exactly_one_not_0_v<multiple<d0,d0>>);
    static_assert(!exactly_one_not_0_v<multiple<d1,d1>>);
    static_assert(!exactly_one_not_0_v<multiple<d0,d1,d1>>);
    static_assert(!exactly_one_not_0_v<multiple<d1,d0,d1>>);
    static_assert(!exactly_one_not_0_v<multiple<d1,d1,d0>>);

Les quatre premières affirmations échouent. Pourquoi ? La spécialisation devrait être capable d'attraper ces cas.

En comparaison, le code suivant fonctionne comme prévu ( http://coliru.stacked-crooked.com/a/871ee1cc28f5ddc9 ):

    #include <iostream>
    template<class T>
    struct multi_pack
    {
          static void f() {std::cout << "base case\n";}
    };

    template<class...Ts1, class...Ts2>
    struct multi_pack<std::pair<std::tuple<Ts1...>, std::tuple<Ts2...>>>
    {
        using T = std::pair<std::tuple<Ts1...>, std::tuple<Ts2...>>;
        static void f() {std::cout << "special case\n";}
    };

Savez-vous pourquoi le premier exemple échoue et le second fonctionne ?

2voto

Giulio Franco Points 1269

Dans le second cas, vous demandez au compilateur de faire correspondre les paquets à l'intérieur de types distincts (il y a une paire d'un tuple de Ts1 et un tuple de Ts2 ). Il n'y a aucune ambiguïté.

Dans votre premier exemple, les deux packs sont utilisés dans le même pack de paramètres, séparés par un troisième type. Je comprends pourquoi vous vous attendez à ce que cela fonctionne, mais apparemment le compilateur (à la fois gcc et clang) refuse de faire correspondre les packs préfixés entièrement.

Dans votre cas particulier, c'est en fait ambigu dans plusieurs cas : puisque V est un paramètre, il peut tout aussi bien être égal à 0, ce qui rend ambiguë la correspondance avec une séquence de d0 . Quoi qu'il en soit, j'ai essayé que même l'utilisation d'une constante 1 ne résout pas le problème. Vous devrez supprimer le préfixe vous-même.

#include <type_traits>
template<class, int> struct dummy{};
template<class...> struct multiple{};

template<class T, class = void> // <-- was this supposed to be an enabler?
struct exactly_one_not_0 : std::false_type {};

template<class Ts, class...RTs, int V>
struct exactly_one_not_0
<
    multiple
    <
        dummy<Ts, V>,
        dummy<RTs, 0>...
    >,
    std::enable_if_t<V!=0>
> : std::bool_constant<V>
{};

template<class LT, class...RDummies>
struct exactly_one_not_0
<
    multiple
    <
        dummy<LT, 0>,
        RDummies...
    >
> : exactly_one_not_0<multiple<RDummies...>>
{};

template<class T>
constexpr bool exactly_one_not_0_v = exactly_one_not_0<T>::value;

using d0 = dummy<int,0>;
using d1 = dummy<int,1>;

static_assert(exactly_one_not_0_v<multiple<d1>>);
static_assert(exactly_one_not_0_v<multiple<d1,d0>>);
static_assert(exactly_one_not_0_v<multiple<d0,d1>>);
static_assert(exactly_one_not_0_v<multiple<d0,d1,d0>>);

static_assert(!exactly_one_not_0_v<multiple<>>);
static_assert(!exactly_one_not_0_v<multiple<d0>>);
static_assert(!exactly_one_not_0_v<multiple<d0,d0>>);
static_assert(!exactly_one_not_0_v<multiple<d1,d1>>);
static_assert(!exactly_one_not_0_v<multiple<d0,d1,d1>>);
static_assert(!exactly_one_not_0_v<multiple<d1,d0,d1>>);
static_assert(!exactly_one_not_0_v<multiple<d1,d1,d0>>);

int main()
{
    return 0;
}

Afin de ne pas instancier plusieurs multiple il est possible que vous souhaitiez les supprimer également

template<class T>
struct exactly_one_not_0 : std::false_type {};

template<class Enable, class... Ts> // <-- was this supposed to be an enabler?
struct exactly_one_not_0_impl : std::false_type {};

template<class... Ts>
struct exactly_one_not_0<multiple<Ts...>>
    : exactly_one_not_0_impl<void, Ts...>
{};

template<class Ts, class...RTs, int V>
struct exactly_one_not_0_impl
<
    std::enable_if_t<V!=0>,
    dummy<Ts, V>,
    dummy<RTs, 0>...
> : std::bool_constant<V>
{};

template<class LT, class...RDummies>
struct exactly_one_not_0_impl
<
    void,
    dummy<LT, 0>,
    RDummies...
> : exactly_one_not_0_impl<void, RDummies...>
{};

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X