Connaitre le type dans un template

Dans un message précédent, je jouais avec les template pour écrire des méthodes génériques dans une classe.
template <typename T>
Cependant, j'aimerais connaitre le type utilisé effectivement dans la fonction lorsqu'elle est compilée (ou exécutée). Par exemple, savoir si le type est signé ou non signé.
Je veux pouvoir générer des nombres aléatoires entre une valeur min et une valeur max, mais je veux avant cela vérifier si les bornes sont compatibles du type employé (pas de négatif si non signé, pas de dépassement de valeur maximale).
Or le type est générique lorsque j'écris ma fonction... Donc comme savoir si le type est signé, ou connaitre les valeurs min et max de mon type dans le code de ma fonction ?
Merci

PS : je précise que je suis sur ESP32.

Bon, je me réponds à moi-même tout seul personnellement :wink:

Serial.println(std::numeric_limits<T>::min());
Serial.println(std::numeric_limits<T>::max());

on n'est jamais mieux servi que.... :slight_smile:

Oui, mais (car le corollaire, c'est "il y a toujours un mais" :slight_smile: ) :

Pour générer un nombre aléatoire, je peux faire :

  • pour un entier : random(10,20) pour un nombre entre 10 et 19 inclus (je vais voir si ça fonctionne pour des négatifs)
  • pour un float : esp_random() / UINT32_MAX pour une nombre entre 0 et 1

Donc, deux façons de générer, selon le type. Il faut donc que je sache le type : une façon est de tester la valeur du max
if (std::numeric_limits<T>::max() > UINT32_MAX) ...
Il y a plus simple ?

Un truc me chagrine dans ce que vous dites :

si vous écrivez

template <typename T>
T nombreAleatoire(T vMin, T vMax) {
  return ...
}

et que vous appelez nombreAleatoire<byte>(a, b);
les éléments a et b auront été promus en byte lors de l'appel avec les règles du compilateur donc dans la fonction vous aurez déjà perdu de l'info.

par exemple

template <typename T>
T nombreAleatoire(T vMin, T vMax) {
  Serial.print(vMin);
  Serial.write(',');
  Serial.print(vMax);
  Serial.print(F(" -> "));
  return (T) random(vMin, vMax);
}

void setup() {
  Serial.begin(115200); Serial.println();
}

void loop() {
  long a = 256, b = 260, r;
  r = nombreAleatoire<uint8_t>(a, b);
  Serial.println(r);
  delay(1000);
}

même si a et b sont des long, une fois arrivés dans la fonction on aura conservé que l'octet de poids faible et le nombre aléatoire est généré entre 0 et 4 et non pas entre 256 et 260

C'est vrai, je n 'avais pas pensé à ça. Il faut donc s'assurer que els bornes sont cohérentes avant d'appeler la fonction. La fonction doit juste savoir quel type de génération elle doit faire. Je vais investiguer esp_random() et random()
Merci du tuyau...

EDIT:
random(a,b) renvoie un nombre entier entre a et b, au format qu'on lui demande (int ou float)
esp_random() renvoie un uint32_t. Pour obtenir un float entre 0 et 1, on peut faire

  uint32_t y = esp_random();
  float z = (float)y/UINT32_MAX;

ou directement:

  float z = esp_random();
  z /= UINT32_MAX;

oui, mais je ne vois pas trop le besoin.

La fonction random arduino prend des long comme paramètres et esp_random() ne prend pas de paramètre et retourne un uin32_t (donc on connait sa taille)

vous voudriez une fonction random qui retourne un double ou un float?

Je veux faire une fonction qui renvoie un entier entre min et max si le type est un entier, et un float si le type est un float

OK... perso j'écrirais 2 fonctions :wink:

sinon vous avez besoin de tester le type dans le template (et typeid() n'est pas supporté à cause des flags de compilation) et donc vous perdez l'intérêt du template

de plus le compilateur risque de se mélanger les pinceaux car un int peut être promu en double

Oui, mais comment savoir quelle fonction appeler ?

Voici ce que j'ai fait pour l'instant :

template <typename T>
const Array<T> &Array<T>::randArray( T min, T max, const int N)
{
	size = N;
	Serial.println(std::numeric_limits<T>::min());
	Serial.println(std::numeric_limits<T>::max());

	if (std::numeric_limits<T>::max() > UINT32_MAX) { // float
		Serial.println("float !");
		for (int i = 0; i < N; ++i) {
			float x = esp_random();
			x /= UINT32_MAX;
			Serial.println(x);
			ptr[i] = min + x * (max - min);
		}
	} else { // not float
		Serial.println("PAS float...");
		for (int i = 0; i < N; ++i) {
			ptr[i] = random(min, max);
		}
	}
	return *this;
}

Il reconnait bien "float" et "PAS float". Mais j'ai ensuite un crash :

CORRUPT HEAP: multi_heap.c:165 detected at 0x3ffb8478
abort() was called at PC 0x40088a17 on core 1

moi je ferais 2 fonctions aleatoireInt() et aleatoireDouble() et laisserais l'appelant se débrouiller

sur un ESP32 si c'est votre architecture cible, vous pouvez sans doute rajouter en début de fonction

template <typename T>
T nombreAleatoire(T vMin, T vMax) {
  if (std::is_floating_point<T>::value) Serial.println("Nombre à virgule");
  ..

is_floating_point va dire vrai si T est float, double ou long double (const ou pas)

mais ça ne marchera pas sur AVR

Merci, je vais tester.

Je cible l'ESP32 car le but est de programmer un perceptron multicouche, de manière plus propre que ma précédente version. Un AVR ne supporterait probablement pas le besoin en mémoire.

Toujours dans les neurones et l’intelligence artificielle à-ce que je vois

Oui, en attendant des processeurs plus appropriés.