Passer d'un concept à une fonction

12

Puisque les concepts sont définis comme des prédicats au moment de la compilation, est-il également possible de réutiliser ces prédicats pour les algorithmes au moment de la compilation? Par exemple, serait-il possible de vérifier si tous les types d'un tuple sont conformes à un concept? Pour autant que je l'ai vu, il n'est pas possible de transmettre un concept à une fonction, ce qui me ramène à l'utilisation de modèles pour ces cas.

#include <type_traits>

template<typename T>
concept FloatLike = std::is_same_v<T, float>;

struct IsFloat
{
    template<typename U>
    constexpr static bool test()
    {
       return FloatLike<U>;
    }
};


template<typename Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate::template test<T>() && ...);
}


int main()
{
   static_assert(all_types<IsFloat, float, float>());
   static_assert(!all_types<IsFloat, float, int>());
}

Ce que je voudrais faire, c'est quelque chose comme ça, donc je n'ai pas à envelopper le concept tout le temps pour pouvoir l'utiliser:

template<concept Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate<T> && ...);
}


int main()
{
   static_assert(all_types<FloatLike, float, float>());
   static_assert(!all_types<FloatLike, float, int>());
}

Existe-t-il un moyen de se rapprocher de cela?

Andreas Loanjoe
la source
Et puis il y aura une proposition pour ajouter des concepts de concepts ... BTW, all_types()peut être considérablement simplifié en utilisant des expressions de repli ... &&:return (... && Predicate::template test<Ts>());
Evg
@Evg ce serait génial :)
Igor R.

Réponses:

5

Existe-t-il un moyen de se rapprocher de cela?

Eh bien non, pas vraiment. Pas en C ++ 20. Il n'y a aucune notion dans le langage actuel d'un modèle-paramètre de modèle. Même les modèles de variables ne peuvent pas être utilisés comme paramètres de modèle. Donc, si nous avons un concept pour commencer, nous ne pouvons pas éviter le wrapping.

Mais ce que nous pouvons faire, c'est écrire des enveloppes plus simples. Si nous acceptons d'utiliser des traits de type "à l'ancienne" comme prédicats, en particulier ceux qui se comportent comme des std::integral_constants, alors nous pouvons avoir des définitions de "concept" assez concises qui peuvent être utilisées comme prédicats.

template<typename T>
using FloatLike = std::is_same<T, float>;

template<template <typename> class Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate<T>{} && ...);
}

C'est aussi bon que possible , pour autant que je puisse voir.

Conteur - Unslander Monica
la source
Est-ce que cela fonctionnerait en décompactant un lambda générique en tant que modèle de modèle de quelque manière que ce soit? Il semble qu'un lambda ne soit jamais un modèle, n'est-ce pas, seulement l'opérateur d'appel?
Andreas Loanjoe
@AndreasLoanjoe - En effet. Un lambda n'est jamais un modèle. Mais si vous êtes prêt à passer des lambdas, alors C ++ 20 vous permet de le faire. Je peux ajouter une variante de cela en quelques minutes.
StoryTeller - Unslander Monica
@AndreasLoanjoe - À bien y penser, une lambda sort toujours très verbeuse. Je ne pense pas que ce soit une excellente alternative. Le voici de toute façon godbolt.org/z/QSHy8X
StoryTeller - Unslander Monica
J'espère qu'ils ajouteront quelque chose de mieux :), mais oui, il semble que ce soit la réponse, seuls les traits de type de style offrent cette fonctionnalité (pas encore).
Andreas Loanjoe
0

Si votre objectif est de "vérifier si tous les types d'un tuple sont conformes à un concept" , vous pouvez faire quelque chose comme ceci:

// concept to check if all types in Ts are the same as T
template<typename T, typename... Ts>
concept AllSame = (std::is_same_v<T,Ts> && ...);

// function only accepts floats as template parameters
template<AllSame<float>... Floats>
constexpr void float_foo()
{
}

// function only accepts ints as template parameters
template<AllSame<int>... Ints>
constexpr void int_foo()
{
}

// function only accepts T as template parameters
template<typename T, AllSame<T>... Ts>
constexpr void foo()
{
}

int main()
{
    int_foo<int, int, int>();
    // int_foo<int, int, double>(); // fails to compile
    float_foo<float, float, float>();
    // float_foo<float, float, int>(); // fails to compile
    foo<int, int, int, int>();
    // foo<int, int, int, float>(); // fails to compile
    foo<double, double, double, double>();
    // foo<double, double, double, int>(); // fails to compile

}

DÉMO EN DIRECT

kanstar
la source
Pourquoi est votre AllSamevariadic? Chaque paramètre de modèle dans un pack introduit par une contrainte de type est déjà contraint séparément.
Davis Herring
@DavisHerring Je ne comprends pas. Voulez-vous dire le concept lui-même ou les paramètres du modèle *_foo()?
kanstar
Je veux dire que le code que vous avez fonctionne si vous supprimez ...le Tset celui && ...qui l'utilise. (De toute évidence, le nom AllSameserait alors inapproprié, mais je ne sais pas pourquoi je voudrais exprimer un décompte en unaire comme de <int,int,int>toute façon.)
Davis Herring
@DavisHerring Ensuite , le concept ne serait pas AllSamemais SameAs(voir en.cppreference.com/w/cpp/concepts/same_as ) et OP voulait avoir un concept qui prend un certain nombre de paramètres variadique de modèle.
kanstar
Ce serait évidemment le cas std::same_as. Je ne pense pas que la partie variadique était le point: c'était l' identité variable (souhaitée) du concept. Et mon point de vue était que l'aspect variadique de votre exemple de concept n'était pas pertinent pour son utilisation (car les concepts non variadiques fonctionnent déjà avec les packs de paramètres de modèle).
Davis Herring