Le problème, c'est qu'il teste si écrire 1 / value est valide au moment de la compilation.
Si je fais 1 / 0; le compilateur me crache une erreur car 0 est un littéral (de même avec un modulo), alors que
constexpr unsigned short zero{ 0u };
1 / zero;
ne cause aucune erreur de compilation (mais au moins un warning), ce qui fait que le concept est toujours vrai.
Il faudrait donc que la valeur soit réécrite directement dans le requires, donc faire une sorte de macro C ou d'inline, mais je n'ai rien vu sur Internet qui en parle (d'autant plus que C++20 est encore tout récent).
Soit ce code :
class Number {
template<typename ValueType>
Number& operator/(ValueType other) requires isNotNull<ValueType> {
// du code
}
};
int main() {
Number n { 4 };
std::cout << n / 0 << std::endl; // operator/ est exécuté quand même
}
* et qu'en est-il des constexpr vector / string, ce lien indique clairement que c'est (déjà) possible en C++20 ? Est-ce reporté à C++23 ?
Petite parenthèse sur std::cout.
Quand on fait
std::cout << quelqueChose << autreChose;
on utilise bien l'opérateur <<, le même que le décalage binaire ?
Si oui, il y a une incohérence, est-il logique (et acceptable) de "détourner" un opérateur qui vraisemblablement ne devrait prendre qu'un entier en paramètre pour lui faire accepter (presque) n'importe quoi d'autre ?
Et dans ce cas, il n'y a aucun décalage au niveau des bits, si ?
Ca ne semble pas être un bon prétexte d'utilisation des concepts. Si vraiment tu veux passer un truc qui n'est pas nul, tu passes par une assertion. Les concepts ont été imaginés pour des contraintes de types, ou intrinsèquement liées au type.
Pour l'histoire de "<<", tu te mélanges les pinceaux. La surchage d'opérateurs nous donne le droit de gérer le comportement des opérateurs en fonction de types de données précis. D'un côté, "<<" sert au décalage de bits, d'un autre, il sert à faire "sortir" des données dans des objets capables de recevoir ces données. Il n'y a aucune incohérence, cet opérateur n'a pas été conçu "juste pour le décalage de bits".
P.S.: "isNotNull" c'est plus le nom d'un trait que d'un concept.
Si de prime abord, c'est extrêmement séduisant, c'est tout autant extrêmement limité dans la pratique.
Restons simple sans introduire de fonction complexe comme un sinus. Supposons que l'on enchaîne des fonctions pour arriver à un 1/(42-abs(x)). Si pour l'inversion, il nous faut le not-null, pour l'étape d'avant il faut le not-42, et celle d'avant un neither-42-nor-minus-42.
> Si oui, il y a une incohérence, est-il logique (et acceptable) de "détourner" un opérateur qui vraisemblablement ne devrait prendre qu'un entier en paramètre pour lui faire accepter (presque) n'importe quoi d'autre ?
On n'est plus à ça prêt depuis le C, on détourne déjà l'opérateur de déréférencement pour lui faire faire des multiplications... Ou encore des additions avec l'opérateur de concaténation en C++, Python...
@Daimyo_, merci pour le lien, j'ai aussi trouvé celui-là (spécifique à VS).
Je n'avais que vaguement vu les traits dans la STL (surtout char_traits en paramètre template de std::basic_string), et je me suis tourné vers std::enable_if qui avait l'air intéressant.
Seulement, il y a un problème : il ne peut évaluer que des expressions constantes, donc forcément valeur dans un template et donc adieu la surcharge d'opérateurs.
@lmghs, j'ai regardé votre lien et je n'ai absolument pas le niveau pour comprendre, ni le topic, ni le code
Pour les assertions, je ne pense pas, j'ai créé deux fonctions pour les messages d'erreur au runtime, ça affiche un message d'erreur, et ça stoppe (ou pas) le programme.
En soi, l'approche des assertions est logique, mais je voulais juste tester une nouvelle fonctionnalité.
J'ai essayé de faire un code avec un trait, je me suis inspiré de l'implémentation de la lib standard pour comprendre leur fonctionnement (std::remove_cv, std::is_arithmetic ...).
Merci pour ta réponse.
Si ma question peut paraître insensée, c'est parce que je travaille sur un projet de fonctions mathématiques (je recommence tout ou presque, voir lien dans ma signature, car code assez mauvais), et j'ai prévu ça pour détecter ce genre d'erreur a la compilation (des que les constexpr string seront supportées (même si le preview de MSVC 16.10 les prend en charge)), tout en gardant une version runtime avec des tests et un affichage personnalisé. (J'aurais peut être du le préciser avant...)
vouloir vérifier une telle assertion à la compilation implique que tous tes calculs doivent être faits à la compilation (du moins tous ceux qui doivent effectuer une cette vérification) . Ecrire une bibliothèque mathématique qui fonctionne à la compilation est assez hardcore. Sans parler du fait qu'en général, les mathématiques en informatique s'utilisent sur des données que le programmeur ne connait pas au moment où il écrit son code. Dit autrement, ta bibliothèque sera limité à une utilisation extrêmement spécifique. Si tu souhaites quand même persister dans cette approche, tu peux regarder du coté de chez boost::hana pour avoir une première une idée de comment ça marche.
Accessoirement, une fonction constexpr peut être employée depuis un contexte dynamique, et les concepts ne serviront à rien ici. Un typage fort sera probablement plus utile, avec les limitations indiquées précédemment. Cf mon précédent post que tu avais trouvé "pointu". Un truc comme ça: https://godbolt.org/z/rTxobfr4T et on voit, que c'est aussi beaucoup s'embêter pour rien que cela ne va pas couvrir les cas où c'est pas-2 qu'il faut recevoir...
@eugchriss, tu me suggères donc d'abandonner constexpr ?
Où y a-t-il des opérations où le mettre est pertinent (factorielle ?)
Et effectivement, ça a l'air assez complexe (je m'encombre déjà d'un namespace math::traits avec des variables template constexpr (accompagnés de plein de requires ici et là), voir ci-dessous)...
@lmghs, je vais essayer de m'inspirer de ton implémentation de la factorielle.
Code foireux que j'ai actuellement :
namespace math::traits {
template<typename ValueType>
using BaseType = std::remove_cvref_t<ValueType>;
template<auto value>
using ValueBaseType = BaseType<decltype(value)>;
template<typename ValueType>
static constexpr bool isMathLibType{ utils::isIn<BaseType<ValueType>, Number> };
template<auto value>
static constexpr bool isMathLibValue{ isMathLibValue<value> };
template<typename ValueType>
static constexpr bool isMathType{ std::is_arithmetic_v<BaseType<ValueType>> || isMathLibType<BaseType<ValueType>> };
template<auto value>
static constexpr bool isMathValue{ isMathType<decltype(value)> };
template<auto value> requires isMathValue<value>
static constexpr bool isNull{ value == 0 };
template<auto value> requires isMathValue<value>
static constexpr bool isStrictlyPositive{ value > 0 };
template<auto value> requires isMathValue<value>
static constexpr bool isPositiveOrNull{ value >= 0 };
template<auto value> requires isMathValue<value>
static constexpr bool isStrictlyNegative{ value < 0 };
template<auto value> requires isMathValue<value>
static constexpr bool isNegativeOrNull{ value <= 0 };
template<auto value> requires isMathValue<value>
static constexpr bool isInteger{ isMathLibValue<value> ? value.isInteger() : std::floorl(value) == value }; // will be constexpr when MathLib types will become constexpr (so when constexpr std::string will be supported)
template<auto value> requires isMathValue<value>
static constexpr bool isNatural{ isPositiveOrNull<value> && isInteger<value> };
}
As-tu vu que le compilateur détecte déjà les divisions par 0 dans les expressions constantes?
Si cela a un sens d'avoir un renforcement de type sur les pointeurs pour avoir des zones où l'on a la certitude qu'un pointeur n'est pas nul, on ne peut pas étendre ces zones aux nombres à cause de `1/x-1`. Rien que là, si x est strictement positif, on perd ces propriétés sur `x-1`. Ce cas est simple. C'est pour cela que depuis le début j'insiste que d'avoir des propriétés fortes sur des nombres c'est séduisant, mais pas vraiment exploitable.
PS: Mais c'est quoi ces static? Il y a confusion avec le Java ici, non?
et (à ma grande surprise, je dois l'admettre), ça ne compile pas (ce qui est logique).
Je vois ce que vous voulez dire quand vous dîtes que c'est très complexe de tester à la compilation chaque cas...
Alors non je n'ai jamais fait à proprement parler de Java (3-4 heures à tout casser avec Android Studio juste avant de démarrer C++, juste pour tester de l'Android), mais j'ai déjà aperçu des codes Java sur Internet, et effectivement ça déborde de static, override etc..
Si j'ai utilisé ce mot-clé ici, c'était pour ne calculer la variable qu'une seule fois, comme dans les classes ou les fonctions :
class Foo {
Type bar_{}; // chaque instance de Foo possède son propre membre bar_
static Type bar2_{}; // toutes les instances de Foo ont accès au membre bar2_
}
Donc si j'ai bien compris, la solution la plus pertinente est de virer tous les constexpr, requires et tout le bordel qui va avec et de les remplacer par des assertions au runtime (ce qui est effectivement logique si constexpr est non-pertinent) ?
On peut tout laisser en constexpr si l'application est plausible.
Ce qui par contre va vite virer au cauchemar, c'est de typer fortement tous les cas possibles, surtout si l'objectif est d'intercepter des domaines de définitions (x >= 0, x != 0, etc). Le langage n'est pas pret pour ça AMA. On s'en sortira mieux avec les contrats en C++23 si tout va bien, avec les assertions en attendant.
Pour static, il a plusieurs utilités en C++:
- une variable globale ou un fonction libre globale déclarée static sera cachée dans l'unité de traduction (.cpp avec tous les #inclue & cie résolus) où elle apparaît. Cas hérité du C.
- une variable locale sera persistante entre 2 appels à la fonctions
- une variable membre ou une fonction membre seront de nouveaux globales mais leur nom sera décoré par le nom de la classe où elles sont déclarées. Elles ne seront pas cachés comme dans le cas des globales.
Et donc, le cas des variables statiques dans un espace de noms ben... c'est un cas bâtard du cas 1. Cela ne correspond à aucun cas d'utilisation désiré que je connaisse -- je n'exclue pas que des gens trouvent une application. Toujours est-il que je doute que cela soit ce que tu cherches à faire.
Bon, j'essaie d'implémenter une factorielle au runtime:
auto factorial(const auto& value) -> decltype(value) {
if (!traits::isMathType<decltype(value)>) {
handleError("Attempt to compute factorial of a non-math typed value (" + utils::toString(value) + ") !", __FILE__, __LINE__);
}
if (value < 0) {
handleError("Attempt to compute factorial of a negative number :\n'" + utils::toString(value) + "'\n", __FILE__, __LINE__);
}
return (value > 0) ? (value * factorial(value - 1)) : 1;
}
et le compilo me fait gentiment remarquer qu'il n'apprécie pas la récursivité dans une fonction auto (d'où le -> decltype(value) qui ne résout pas le problème).
Donc la seule autre solution serait de faire une fonction avec un paramètre long double (le type builtin le plus large) et autant de surcharges que de types de ma bibliothèque (à moins de faire une classe mère abstraite MathType avec aucune fonction, mais pour passer un const MathType& en paramètre et n'avoir qu'une surcharge qui utilise l'héritage ?).
Mea culpa, on peut faire des choses avec le C++20, mais c'est plus de strong-types que de concepts que l'on va véritablement avoir besoin AMA.
Cela m'a inspiré de la sorte: https://godbolt.org/z/cfhPc3xjx (attention, il faut le GCC du trunk pour que cela compile). Mais il reste encore une limitation: comment convertir automatiquement 42.5 en `set<double, 42.5, 4.5>` ,
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Liens utiles pour le C++
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
Liens utiles pour le C++
Liens utiles pour le C++
Liens utiles pour le C++
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
Liens utiles pour le C++
Eug
Liens utiles pour le C++
Liens utiles pour le C++
Liens utiles pour le C++