Templates mit Ausschlüssen- geht das?

Mal wieder ein grundlegendes Thema für die Template-Gurus:

Ich würde über Templates eine Klasse definieren wollen, die für uint8_t bis uint64_t gültig ist, also für ganzahlige Typen, aber nicht für Fließkommatypen (float/double).
Im Moment fehlt mir die Idee, wie ich die Fließkommatypen ausschließen könnte.

Gruß Tommy

Nennt sich “Type Traits”.
Du könntest dieses Beispiel etwas aufpeppen

Und dann, zur Compilezeit mit static_assert() auswerten

Erkennen ob ein Typ ein Integer oder eine Gleitkommazahl ist hatten wir gerade erst hier:

Dann static_assert verwenden um einen Fehler auszulösen

Auf die Schnelle schaffe ich es aber nicht das zusammen zum Laufen zu bekommen. z.B. meint der Compiler die constexpr Funktion wäre nicht konstant wenn ich sie in static_assert einsetzen will

constexpr Funktion wäre nicht konstant wenn ich sie in static_assert einsetzen will

Ist mir auch gerade passiert…
Weiß noch nicht warum.

Aber geht ja auch ohne die Funktion.

Auf die Schnelle mal eben zurecht gedengelt:

template<typename T, typename U>   //Typen ungleich, gibt 0 zurück
struct is_same
{
  enum { value = 0 };
};

template<typename T>   //Typen gleich, gibt 1 zurück
struct is_same < T, T >
{
  enum { value = 1 };
};

template<typename T>
class TestKlasse
{
   private:
   T datenFeld[41];

   public:
   TestKlasse()
   {
      static_assert(not (is_same<float,T>::value || is_same<double,T>::value), "Float Typen verboten");
   }
};

//TestKlasse<float> testA;
TestKlasse<int> testB;



void setup() 
{
}

void loop() 
{
}

Es geht doch :slight_smile:

template<typename T, typename U>
struct is_same
{
  enum { value = 0 };

  constexpr operator bool() {
    return value;
  }
};

template<typename T>
struct is_same <T, T>
{
  enum { value = 1 };

  constexpr operator bool() {
    return value;
  }
};

template<typename T>
struct is_integer
{
  constexpr operator bool()
  {
    return is_same<int, T>() || is_same<long, T>();
  }
};
#include "Templates.h"

template <typename T>
class Test
{
public:
  T data;

  static_assert(is_integer<T>(), "Fehler. Keine Gleitkommazahlen erlaubt");
};

void setup()
{
  Test<int> test1;
  Test<float> test2;
}

void loop()
{
}

Mit Klassen scheint der Compiler das also wirklich alles zur Compile-Zeit zu schaffen. Wird auch ein Grund sein weshalb so viele dieser Hilfs-Templates als Strukturen ausgeführt sind

Fein...
Werde ich mal genauer untersuchen, woran das liegt.
(irgendwann mal)

Ich danke Euch.

Gruß Tommy

Hallo,

ich staune auch wie das Serenifly immer so nachbaut. Ich danke ebenfalls, kann man irgendwann gebrauchen.

Habe das nochmal umgearbeitet…
Ob das jetzt “besser” ist, weiß ich wirklich nicht.

Es ist jetzt, auf jeden Fall, deutlich “rechteckiger”!

template<bool b> struct bool_type{constexpr operator bool(){return b;}};
struct true_type   :bool_type<true> {};
struct false_type  :bool_type<false>{};

template<typename T> struct is_floatingpoint         :false_type{};
template<>           struct is_floatingpoint<float>  :true_type {};
template<>           struct is_floatingpoint<double> :true_type {};

template<typename T> struct is_signed             :false_type{};
template<>           struct is_signed<int8_t >    :true_type {};
template<>           struct is_signed<int16_t>    :true_type {};
template<>           struct is_signed<int32_t>    :true_type {};
template<>           struct is_signed<int64_t>    :true_type {};

template<typename T> struct is_unsigned           :false_type{};
template<>           struct is_unsigned<uint8_t > :true_type {};
template<>           struct is_unsigned<uint16_t> :true_type {};
template<>           struct is_unsigned<uint32_t> :true_type {};
template<>           struct is_unsigned<uint64_t> :true_type {};

template<typename T> struct is_integral :bool_type<is_unsigned<T>()||is_signed<T>()>{};

template<typename T, typename U> struct is_same      :false_type{};
template<typename T>             struct is_same<T,T> :true_type {};


// ---------------

template<typename T>
class TestKlasse
{
  static_assert(is_floatingpoint<T>(), "Fliesskomma Type erwartet");
  //static_assert(is_integral<T>(), "Ganzzahliger Type erwartet");
  //static_assert(is_signed<T>(), "Ganzzahliger Type, mit Vorzeichen, erwartet");
  //static_assert(is_unsigned<T>(), "Ganzzahliger Type, ohne Vorzeichen, erwartet");
};

TestKlasse<float> t;
//TestKlasse<int> t;
//TestKlasse<long> t;
//TestKlasse<unsigned long> t;

void setup() 
{
}

void loop() 
{
}

Also quasi erstmal für alle allgemeinen Typen ein Template mit false als Rückgabewert und dann über die spezialisierten Templates das true einrichten.
Das ist sicher eine klare Ansage.

Da habe ich ja mit meiner Frage, die noch nichtmal einen aktuellen praktischen Hintergrund hatte, einiges los getreten.
Das ist aber etwas, was mir hier gefällt. Auch auf solche Fragen bekommt man wertvolle Antworten, aus denen man viel lernen kann.

Gruß Tommy

Ja. Da wird ausgenutzt dass bei der Auflösung der Templates die am meisten spezialisierte Variante genommen wird.
Schöne Lösung die die Auflistung der verschiedenen Datentypen etwas übersichtlicher macht

Oben noch eine Ersetzung gemacht:

//entnommen
//template<typename T>struct is_integral{constexpr operator bool(){return is_unsigned<T>()||is_signed<T>();}};

// eingebaut
template<typename T>struct is_integral :bool_type<is_unsigned<T>()||is_signed<T>()>{};

Da habe ich ja mit meiner Frage, die noch nichtmal einen aktuellen praktischen Hintergrund hatte, einiges los getreten.
Das ist aber etwas, was mir hier gefällt.

Mir auch…
Ich bin wahrlich bei dem Thema noch nicht sattelfest und darum dankbar für solche Anlässe.

Das ist sicher eine klare Ansage.

So war es gedacht.

Schöne Lösung die die Auflistung der verschiedenen Datentypen etwas übersichtlicher macht

Danke, für die Blumen.

Hallo,

ich schaue mir das schon eine Weile an. Aber ich weiß überhaupt nicht wie ich das praktisch nutzen soll?
Was mache ich denn dann mit den hier erstellen Objekten test1 bzw. t? :o

Wenn Du ein Klassentemplate bauen willst, dass für alle ganzzahligen Typen char/byte bis unsigned long gültig sein soll, aber nicht für Fließkommazahlen.

Also z.B. Eine Zahl mit Min- und Maxgrenzen. Braucht keiner, mir fällt nur gerade nichts sinvolles ein.

Gruß Tommy

Hallo,

achso, dass ist in der Form noch nicht verwendbar. Mit den Objekten bzw. Datentyp muss man erst neue Template Klassen erstellen?

Weil als ich eine ähnliche Frage gestellt hatte Wie kann man typeid nutzen? - Deutsch - Arduino Forum konnte ich das mit den Mehoden gleich anwenden. Deswegen kam ich jetzt nicht zu Rande.

Doc_Arduino:
Hallo,

ich schaue mir das schon eine Weile an. Aber ich weiß überhaupt nicht wie ich das praktisch nutzen soll?
Was mache ich denn dann mit den hier erstellen Objekten test1 bzw. t? :o

Was du damit machen kannst…

Angenommen, manche Dinge würden nur mit bestimmten Typen Sinn machen…
z.B. ein Array Index, der darf nie negativ werden.
Und du möchtest den Typ des Index flexibel halten. Per Template.
Also uint8_t usw erlauben
Aber int usw verbieten.
Und float würde ebenso sinnfrei sein.

Du hättest 3 Möglichkeiten…

  • Die Gefahr aussitzen
  • Zur Laufzeit auf index<0 abfragen und einen Fehler melden, Programm abbrechen.
  • Oder diese Type Traits verwenden und es dir schon vom Kompiler um die Ohren hauen lassen

Doc_Arduino:
Was mache ich denn dann mit den hier erstellen Objekten test1 bzw. t?

Was man halt so mit Objekten macht :stuck_out_tongue:

Es ging darum einen Compiler-Fehler zu erzeugen wenn man die Klasse in einer bestimmten Weise verwendet. Man kann so z.B. auch verhindern dass jemand riesige Arrays erzeugt.

Hallo,

ich bin für
[ x ] aussitzen

:smiling_imp: :smiling_imp: :smiling_imp:

Ich sage einmal vorsichtig langsam dämmerts. Also die Datentypüberprüfung selbst ist mir schon klar. Nur das weiter anwenden/einbauen daran scheitert es noch, habe eine Denkblockade. Vielleicht löst die sich noch übers WE.

Also ganz grob würde ich mir das bei einer Indexklasse so vorstellen:

template<typename T>
class Index
{
   public:
   Index() {
     static_assert(not is_unsigned<T>(), "Error, kein gültiger Indextyp");
   }
   T getIdx() {return idx;}
   void setIdx(T val) {idx = val;}
 
   void incIdx() { idx++;}

   void decIdx() { idx--;}
 
   private:
     T idx = 0;
};

Das ist auf die Schnelle aber wohl noch zu kurz gedacht.

Wahrscheinlich müsste man eine ganze Reihe an Operatoren überladen, wie = (lvalue/rvalue), +=, ++/-- (Präfix und Postfix), usw. wenn man diese Klasse wirklich sinnvoll nutzen wollte.

Damit könnte man dann Indexe als uint8_t, uint16_t, … uint64_t erzeugen.

Gruß Tommy

static_assert(not is_unsigned(), “Error, kein gültiger Indextyp”);

Also eher so:

template<typename T>
class Index
{
  static_assert(is_unsigned<T>(), "Error, kein gültiger Indextyp");
   public:
   Index() {
    // static_assert(not is_unsigned<T>(), "Error, kein gültiger Indextyp");
   }
   T getIdx() {return idx;}
   void setIdx(T val) {idx = val;}
 
   void incIdx() { idx++;}

   void decIdx() { idx--;}
 
   private:
     T idx = 0;
};

Es ist recht egal, wo es steht…
Muss also nicht in den Konstruktor.