Salut à tous,
C++20 apporte les concepts, mais par pure nostalgie, regardons comment émuler ça en C++11
#include <utility>
#define REQUIRES(x) class _ = decltype(x)
template<class T,
REQUIRES(((std::declval<T>()==0), T(1), std::declval<T>() * (std::declval<T>() -1)))>
T fact(T const& n) {
return n == 0 ? T(1) : n * fact(n -1);
}
auto x = fact(3);
// auto y = fact("er");
Dans le REQUIRES
, on fout juste les expressions qu'on souhaite être valides pour note fonction, et hop, si une ds expressions est non valide, on a un message d'erreur et la fonction sera ignorée lors de la recherche des surcharges possibles de la fonction.
Note : petit code sans prétention, faillible a plein d'égards, mais pas moins amusant
# Typage structurel
Posté par barmic 🦦 . Évalué à 2.
Ça me fait beaucoup penser à du typage structurel, il y a une différence qui m'échappe ?
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Typage structurel
Posté par Firwen (site web personnel) . Évalué à 2. Dernière modification le 17 septembre 2020 à 15:44.
Ça l'est: Les concepts sont une forme de typage structurel si c'est ta remarque.
On peut voir les concepts comme des assertions sur du typage structurel exécuté à la compilation.
Le typage structurel a toujours existé en C++. Mais avant C++20, il était basé principalement sur des assomptions implicites et les : Il n'était pas déclaré de manière formelle.
Ce qui indirectement était souvent responsable des erreurs de compilation de 45km légendaires en C++
[^] # Re: Typage structurel
Posté par barmic 🦦 . Évalué à 2.
Tu aurais un exemple pour que ce soit plus clair pour moi ?
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Typage structurel
Posté par Guillaum (site web personnel) . Évalué à 3.
};
Si plus loin tu appelle
foo(10)
, on va récuperer une erreur lors dea.baz()
disant que le type int n'a pas de méthodebaz
. Les assomptions surT
sont implicites. Et le problème c'est que cela peut se propager très très loin. Si l'appel àbaz
se fait après plusieurs résolution de type / appels de fonctions qui travaillent surT
, tu vas récupérer une erreur dans un bout de code que tu ne maîtrise pas du tout et largement hors du contexte. Alors que on peut supposer que le code defoo
est correct et que le problème est au niveau dea.baz()
.En étant plus explicite sur le type de
foo
, l'erreur sera sans doute plus ciblée.[^] # Re: Typage structurel
Posté par barmic 🦦 . Évalué à 2.
Ah oui je vois très bien. Je suis un peu trop habitué à Java qui n'a pas du tout ce comportement.
Pour le problème de l'erreur c'est ce qui cause les erreurs très longues, non ? C'est aussi quelque chose qui peut être embêtant avec les langages qui utilisent massivement l'inférence de type. Si tu ne prends vraiment jamais le temps de définir tes types, une erreur peut se montrer que très loin.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Typage structurel
Posté par freem . Évalué à 2.
C'est bien le souci.
Les templates sont censés être génériques, donc deviner le type. C++11 a sauvé ce qu'il restait de ma santé mentale, au point que je l'ai adopté en 2008 pour mes projets perso… et je n'ai toujours pas adopté le C++17 en 2020!
C++11 (et clang aussi, ne pas oublier clang putain) a considérablement amélioré ces horreurs d'erreurs de templates (même si le gcc que j'utilise est toujours à la traîne sur le sujet).
[^] # Re: Typage structurel
Posté par gorbal . Évalué à 2.
Quand une classe/fonction attend comme paramètre une classe ou un objet ayant une particularité. En c++ 03 il n'y a aucun moyen de la préciser.
Si la contrainte n'est pas respectée le compilateur renvoie alors un message d'erreur au moment de l'utilisation de la particularité. Ce qui était rarement clair.
En C++ 20 on peut préciser lors de la déclaration de la classe/fonction ce qui est attendu.
Ex pour une fonction similaire à std::find_if qui attend qui attend un objet 'p' possédant un fonction "comp()" retournant un bool.
En C++ 03:
la classe P n'a aucune contrainte précisée dans la définition de la fonction.
En C++ 20:
En C++ 20 on peut définir MyPredicate avec la contrainte obligeant l'objet à posséder une fonction "comp()".
Si jamais la condition n'est pas respectée, voici le genre d'erreurs retournées dans GCC 10:
[^] # Re: Typage structurel
Posté par barmic 🦦 . Évalué à 2.
Oui au final les concepts c'est moins l'ajout d'un typage structurel que la possibilité de le contractualiser.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Typage structurel
Posté par Zenitram (site web personnel) . Évalué à 2.
Euh… ca fait quand même bizarre, j'ai l'impression de devoir faire à la main un truc que le compilo pourrait me faire automatiquement avec un message moins abscons, car le message d'erreur semble dire la même chose que la contrainte manuelle, juste que le message est formaté dans un style super artificiel alors qu'il pourrait être plus sympathique (le message dit en gros qu'il manque la comparaison).
Qu'ai-je loupé?
[^] # Re: Typage structurel
Posté par barmic 🦦 . Évalué à 4.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Typage structurel
Posté par kantien . Évalué à 5. Dernière modification le 18 septembre 2020 à 01:19.
T'as le message d'erreur d'un type checker au lieu d'avoir une stack trace à la python. En gros, le système de template c'était du lamba-calcul non typé, ils y ont rajouté un système de types. Le langage est interprété par le compilateur : en l'absence de typage statique, tu as une stack trace lors de l'appel, avec un système de types tu as tous les avantages du typage statique.
Par contre le choix du nom pour la fonctionnalité n'est pas très judicieux : un concept c'est un type (les deux mots sont synonymes), donc des concepts il y en avait déjà depuis le début en C++. Là, cela revient à paramétrer du code par un type muni de certaines opérations : ça s'appelle une algèbre. ;-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par Guillaum (site web personnel) . Évalué à 6.
Pas grand chose à vrai dire.
En premier lieu, les concepts vont te permettre de forcer "un peu plus" que ce que ta fonction demande vraiment. Par exemple si ta fonction ressemble à:
template T Foo(T a, T b){
return a + b;
}
Le compilateur sait que tu veux un operator+ sur T, mais c'est tout. Avec un concept tu pourra aussi demander à ce que + soit commutatif.
En second lieu, le compilateur sait des choses en observant ton code. De la même manière que tu sais des choses en observant un code. Mais il y a une partie ambigu qu'un lecteur (humain ou compilateur) ne pourra pas décider sans aide. Si j'appelle Foo en lui passant pour T une table qui ne possède pas d'opérateur +.
Est-ce le code de Foo qui est faux ? Est-ce que type T devrait avoir un opérateur + ou est-ce que c'est mon appel qui est faux et je voulais en fait additionner le prix des tables ? Pour chacun c'est une erreur différente avec une position différente.
En précisant grace à un concept ce que tu attend, tu vas pouvoir supprimer tout un ensemble d'erreur, réduisant ainsi le bruit.
Bref, c'est autant un moyen d'améliorer les erreurs que de rajouter (facilement) des contraintes additionnelles.
[^] # Re: Typage structurel
Posté par kantien . Évalué à 3.
C'est fascinant ça. Mais comment on prouve au type checker que l'opérateur
+
que l'on définit est bien commutatif ? On se contente de l'affirmer sans preuve ? Dans les faits, elle ressemble à quoi la syntaxe pour ce genre de propriété ?Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par Guillaum (site web personnel) . Évalué à 2.
En coq, cela pourrait ressembler à ça:
Theorem plus_est_commutatif: forall a, b. a + b = b + a.
Proof. Admitted.
;) Mais je pense amicalement que tu me provoques un peu sachant que tu sais cela très bien.
Plus sérieusement,
Oui. En Haskell, chaque "class" (qui est ce qui se rapproche le plus des concepts du C++) vient souvent avec des "lois" qu'un utilisateur peut s'attendre à voir pris en compte par tout type de la class. Par exemple
map f (map g l)
doit être la même chose quemap (f.g) l
. Appliquerg
puisf
sur un chaque élément de "la collection"l
doit renvoyer la même chose qu'appliquer la composition de f et g (f.g
) sur chaque élément de l.Mais le langage ne le vérifie absolument pas. C'est laissé à la charge du développeur d'être cohérent et de s'assurer du respect des lois. Il existe des outils de tests qui peuvent tenter de prouver les propriétés, soit avec un solveur type z3, soit par génération de valeur aléatoires.
Mais alors, cela apporte quoi de plus ?
C'est juste un problème d'interface ou de contrat que tu passes avec ton développeur et la libraire. Si on reprend l'exemple de la commutativité, si en tant que développeur je sais que le type générique (e.g. template) que j'utilise est commutatif pour mon opération, alors je peux me permettre certaines choses. On peut me mentir, mais au moins j'aurais prévenu. Inversement, en demandant que le type en entrée soit commutatif, je préviens l'utilisateur et il ne peut pas rater la contrainte que je lui demande.
[^] # Re: Typage structurel
Posté par kantien . Évalué à 3.
Ce n'était pas de la provocation, j'étais bien sérieux avec ma question. Je me doutais bien que cela devait être affirmé sans preuve, mais j'ai préféré avoir confirmation. Là où j'étais un peu railleur, c'est en te demandant la syntaxe choisie pour exprimer cela (de manière générale je me demandes quels genres de psychotropes prennent les responsables du standard C++, mais ils devraient réduire la dose, j'ai rarement vu une syntaxe aussi laide).
Ça se tient. C++ étant orienté performance, avoir la commutativité permet de choisir un parcours de données plus efficace pour optimiser l'usage du cache du CPU, par exemple.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par Guillaum (site web personnel) . Évalué à 3.
;)
La commutativité et l'associativité sont des points super importants en calcul sur des gros volumes. Que ce soit en C++ ou en n'importe quoi.
[^] # Re: Typage structurel
Posté par kantien . Évalué à 3.
Ce n'est pas moi qui dirait le contraire : ces propriétés permettent d'effectuer les calculs dans n'importe quel ordre, i.e. quelque soit la permutation effectuée sur la séquence d'opérations on aboutit toujours au même résultat. Ainsi, on est moins tributaire du temps d'accès aux données (peu importe l'ordre dans lesquelles elles arrivent) que ce soit du à de la latence réseau, ou à des échanges répétés entre le cache et la ram. ;-)
Il y a une série d'articles de blogs (compilé en un livre) sur le thème théorie des catégories pour les programmeurs. L'auteur y donne des illustrations de code en Haskell et C++ : c'est dingue à quel point la syntaxe du C++ est tordue et peu « parlante ». Si l'on prend la notion élémentaire de monoïde (cf chapitre 3), en Haskell cela se définit ainsi :
autrement dit un monoïde sur un type
m
est la donnée d'un élément neutre et d'une opération associative (important l'associativité, même si le contrat est implicite entre les programmeurs ;-). Là où en C++, avec les concepts, on se retrouve à le définir ainsi :Non, mais what the fuck !? :-o
Par comparaison, en ML, c'est proche du Haskell mais plus conforme à la définition formelle des mathématiciens :
c'est-à-dire la donnée conjointe d'un type (ou concept, ou ensemble…), d'une loi de composition interne associative et d'un élément neutre pour cette loi. La différence avec Haskell étant que le type fait partie du dictionnaire, là où en Haskell le type support du monoïde paramétrise le dictionnaire qui n'a que deux éléménts. C'est similaire à ce type produit :
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par Blackknight (site web personnel, Mastodon) . Évalué à 3.
Moi ça me rappelle les generics en Ada
Ai-je bien compris ?
Peut-on faire des trucs encore plus forts avec les concepts que juste spécifier les opérations nécessaires ?
[^] # Re: Typage structurel
Posté par kantien . Évalué à 3.
Oui c'est cela, mais plus proche des packages génériques : c'est tout une section de code qui peut être paramétrée, pas seulement une fonction. Mais les concepts n'ajoutent rien de nouveau là-dessus, c'est ce qu'on toujours fait les template. Les concepts c'est juste un système de types pour les template. Le langage des template est interprété par le compilateur et, avant, lorsqu'il y avait une problème lors de l'instantiation d'une template, il y a avait l'équivalent d'une stack trace en guise de message d'erreur, qui pouvait faire 3km de long si le code utilisé était très générique. Là, ils auront des messages plus clairs du au système de type.
Je ne crois pas, mais on peut les utiliser en dehors des templates. Par exemple, pour contraindre le mécanisme d'inférence de type :
ne compilera que si la fonction
f
retourne une valeur que l'on peut trier.Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par Clément V . Évalué à 3.
Pour compléter les autres réponses, il y a un mécanicisme un peu plus complexe mais plus puissant (et qui est celui utilisé dans le journal). Il se base sur une règle du C++ abrégée en SFINAE (Substitution Failure Is Not An Error). L'idée est que si, lors de la substitution d'un type dans un template, on obtient une déclaration que n'a pas de sens, le compilateur ignore la fonction au lieu d'émettre une erreur. Ça ne marche que dans la déclaration (paramètres du template, type de retour, ou types des paramètres), pas dans le corps de la fonction.
Ce qui donne :
J'ai un type imbriqué A
J'ai un type imbriqué B
Pas d'erreurs puisqu'à chaque fois exactement une des fonctions est gardée. Si un type avait à la fois une type imbriqué A et B, les deux fonctions seraient valide et le compilateur générerait une erreur comme il ne saurait pas choisir. Si un type n'a aucune des deux propriétés, les deux fonctions sont ignorées et le compilateur génère une erreur disant que f n'existe pas (mais s'il est sympa, il t'explique pourquoi il a ignoré les deux possibilités avec un message à rallonge).
Ça permet d'avoir des fonctions surchargées qui sélectionnent la bonne alternative en fonction des propriétés d'un type paramètre du template. Évidemment, si la condition que tu veux avoir n'est pas un type que tu utilises en retour ou en paramètre, ça devient plus compliqué mais beaucoup de choses sont possibles. Les concepts de C++20 simplifient largement tout ça.
Pour aider à l'utilisation avancée du SFINAE pré-C++20, il y avait quelques aides comme enable_if et une TS proposait d'ajouter des détecteurs (mais c'est sûrement devenu obsolète avec les concepts).
# Ça compile sous Windows ?
Posté par Eh_Dis_Mwan . Évalué à -5.
Si oui : pourquoi ne pas penser à créer un lait. cppfr.org ?
[^] # Re: Ça compile sous Windows ?
Posté par freem . Évalué à 2.
Parce que les moules préfèrent la bière libre au lait.
[^] # Re: Ça compile sous Windows ?
Posté par Eh_Dis_Mwan . Évalué à 1.
Il laisse leur gd père faire ?
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.