C++ 14 permettra la création de variables modélisées. L'exemple habituel est une variable 'pi' qui peut être lue pour obtenir la valeur de la constante mathématique π pour différents types (3 pour int
; la valeur la plus proche possible avec float
, etc.)
En plus de cela, nous pouvons avoir cette fonctionnalité simplement en enveloppant une variable dans une structure ou une classe basée sur un modèle, comment cela se mélange-t-il avec les conversions de types? Je vois des chevauchements.
Et à part l'exemple pi, comment cela fonctionnerait-il avec des variables non const? Un exemple d'utilisation pour comprendre comment tirer le meilleur parti d'une telle fonctionnalité et quel est son but?
Et à part l'exemple pi, comment cela fonctionnerait-il avec des variables non const?
Actuellement, il semble instancier les variables séparément pour le type. c'est-à-dire que vous pouvez attribuer 10 à n<int>
et ce serait différent de la définition du modèle.
template<typename T>
T n = T(5);
int main()
{
n<int> = 10;
std::cout << n<int> << " "; // 10
std::cout << n<double> << " "; // 5
}
Si la déclaration est const
, elle est en lecture seule. Si c'est un constexpr
, comme toutes les déclarations constexpr
, il n'a pas beaucoup d'utilité en dehors de constexpr
(ressions).
En plus de cela, nous pouvons avoir cette fonctionnalité simplement en enveloppant une variable dans une structure ou une classe basée sur un modèle, comment cela se mélange-t-il avec les conversions de types?
Il s'agit d'une simple proposition. Je ne peux pas voir comment cela affecte les conversions de type de manière significative. Comme je l'ai déjà dit, le type de la variable est le type avec lequel vous avez instancié le modèle. c'est-à-dire que decltype(n<int>)
est int. decltype((double)n<int>)
est double et ainsi de suite.
Un exemple d'utilisation pour comprendre comment tirer le meilleur parti d'une telle fonctionnalité et quel est son but?
N3651 fournit une justification succincte.
Hélas, les règles C++ existantes ne permettent pas à une déclaration de modèle de déclarer une variable. Il existe des solutions de contournement bien connues pour ce problème:
• utiliser des données statexpr membres des modèles de classe
• utiliser des modèles de fonctions constexpr renvoyant les valeurs souhaitées
Ces solutions de contournement sont connues depuis des décennies et bien documentées. Les classes standard telles que std :: numeric_limits sont des exemples archétypaux. Bien que ces solutions de contournement ne soient pas parfaites, leurs inconvénients étaient tolérables dans une certaine mesure, car à l'ère C++ 03, seules les constantes de types simples et intégrées bénéficiaient d'une prise en charge directe et efficace du temps de compilation. Tout cela a changé avec l'adoption de variables constexpr en C++ 11, qui ont étendu la prise en charge directe et efficace aux constantes de types définis par l'utilisateur. Désormais, les programmeurs rendent les constantes (de types de classe) de plus en plus apparentes dans les programmes. Augmentez donc la confusion et les frustrations associées aux solutions de contournement.
...
Les principaux problèmes avec "membre de données statiques" sont:
• ils nécessitent des déclarations "en double": une fois à l'intérieur du modèle de classe, une fois à l'extérieur du modèle de classe pour fournir la définition "réelle" au cas où la constante serait utilisée.
• les programmeurs sont à la fois vexés et confus par la nécessité de fournir deux fois la même déclaration. En revanche, les déclarations constantes "ordinaires" n'ont pas besoin de déclarations en double.
...
Des exemples bien connus dans cette catégorie sont probablement les fonctions membres statiques de numeric_limits, ou des fonctions telles que
boost::constants::pi<T>()
, etc. Les modèles de fonctions Constexpr ne souffrent pas du problème de "déclarations en double" que les membres de données statiques ont; en outre, ils fournissent une abstraction fonctionnelle. Cependant, ils forcent le programmeur à choisir à l'avance, sur le site de définition, comment les constantes doivent être délivrées: soit par une référence const, soit par un type non référence simple. Si elles sont livrées par référence const, les constantes doivent être systématiquement allouées en stockage statique; si par type non référence, les constantes doivent être copiées. La copie n'est pas un problème pour les types prédéfinis, mais c'est un outil incontournable pour les types définis par l'utilisateur avec une sémantique de valeur qui ne se limite pas à de minuscules types prédéfinis (par exemple, matrice, ou entier, ou bigfloat, etc.) En revanche, " les variables const (expr) ordinaires ne souffrent pas de ce problème. Une définition simple est fournie, et la décision de savoir si les constantes doivent réellement être mises en page dans le stockage dépend uniquement de l'utilisation, pas de la définition.
nous pouvons avoir cette fonctionnalité simplement en enveloppant une variable dans une structure ou une classe basée sur un modèle
Oui, mais ce serait du sel syntaxique gratuit. Pas sain pour la pression artérielle.
pi<double>
exprime mieux l'intention que pi<double>::value
. Court et précis. C'est une raison suffisante dans mon livre pour autoriser et encourager cette syntaxe.
Un autre exemple pratique pour les modèles de variables de C++ 14 est lorsque vous avez besoin d'une fonction pour passer quelque chose dans std::accumulate
:
template<typename T>
T const & (*maxer) (T const &, T const &) = std::max<T>;
std::accumulate(some.begin(), some.end(), initial, maxer<float>);
Notez que l'utilisation de std::max<T>
est insuffisant car il ne peut pas déduire la signature exacte. Dans cet exemple particulier, vous pouvez utiliser max_element
à la place, mais le fait est qu'il existe toute une classe de fonctions qui partagent ce comportement.
Je me demande si quelque chose dans ce sens serait possible: (en supposant la disponibilité des modèles lambdas)
void some_func() {
template<typename T>
std::map<int, T> storage;
auto store = []<typename T>(int key, const T& value) { storage<T>[key] = value; };
store(0, 2);
store(1, "Hello"s);
store(2, 0.7);
// All three values are stored in a different map, according to their type.
}
Maintenant, est-ce utile?
Comme utilisation plus simple, notez que l'initialisation de pi<T>
Utilise une conversion explicite (appel explicite d'un constructeur unaire) et non une initialisation uniforme. Ce qui signifie que, étant donné un type radians
avec un constructeur radians(double)
, vous pouvez écrire pi<radians>
.
Eh bien, vous pouvez l'utiliser pour écrire du code temporel de compilation comme ceci:
#include <iostream>
template <int N> const int ctSquare = N*N;
int main() {
std::cout << ctSquare<7> << std::endl;
}
Il s'agit d'une amélioration significative par rapport à l'équivalent
#include <iostream>
template <int N> struct ctSquare {
static const int value = N*N;
};
int main() {
std::cout << ctSquare<7>::value << std::endl;
}
que les gens écrivaient pour effectuer la métaprogrammation des modèles avant l'introduction des modèles de variables. Pour les valeurs non-type, nous avons pu le faire depuis C++ 11 avec constexpr
, donc les variables de modèle n'ont que l'avantage d'autoriser des calculs basés sur des types dans les modèles de variable.
TL; DR: Ils ne nous permettent pas de faire quoi que ce soit que nous ne pouvions pas faire auparavant, mais ils rendent la métaprogrammation de modèle moins un PITA.