mikrocontroller.net

Forum: PC-Programmierung C++ Template-Spezialisierung?


Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi, ich würde gerne abhängig davon, ob ein Typ floating point ist oder 
nicht, ein Template spezialisieren:  Für Floats soll einfach x*x 
berechnet werden, ansonsten x.square():


#include <type_traits>

template<typename T>
inline T squareX (const T &x)
{
    return squareXX<T, std::is_floating_point<T>::value> (x);
}

double testSquare (double x)
{
    return squareX<double> (x);
}

squareX ist ein Wrapper, der abhängig von der Klassifizierung von T 
entweder squareXX<T,true> oder squareXX<T,false> verwenden soll.
template<typename T, bool B>
static inline T squareXX (const T &x);

template<typename T>
inline T squareXX<false> (const T &x)
{
    return x.square();
}

template<typename T>
inline T squareXX<true> (const T &x)
{
    return x * x;
}
x.cpp:180:37: error: template-id 'squareXX<false>' in declaration of primary template
 inline T squareXX<false> (const T &x)
                                     ^
x.cpp:186:36: error: template-id 'squareXX<true>' in declaration of primary template
 inline T squareXX<true> (const T &x) { return x * x; }
                                    ^
x.cpp:186:10: error: redefinition of 'template<class T> T squareXX(const T&)'
 inline T squareXX<true> (const T &x) { return x * x; }
          ^
x.cpp:180:10: note: 'template<class T> T squareXX(const T&)' previously declared here
 inline T squareXX<false> (const T &x)
          ^
x.cpp:174:17: warning: inline function 'T squareXX(const T&) [with T = double; bool B = true]' used but never defined
 static inline T squareXX (const T &x);/*
                 ^
x.cpp:174:17: warning: inline function 'T squareXX(const T&) [with T = Fix<15>; bool B = false]' used but never defined

Wie geht das richtig?

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Theoretisch so:
template<typename T, bool B>
static inline T squareXX (const T &x);

template<typename T>
inline T squareXX<T, false> (const T &x)
{
    return x.square();
}

template<typename T>
inline T squareXX<T, true> (const T &x)
{
    return x * x;
}

ABER: Du kannst Funktionen nicht partiell spezialisieren.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So gehts:
template<typename T>
T squareX (const T &x)
{
    if constexpr(std::is_floating_point<T>::value) {
        return x * x;        
    }
    else {
        return x.square();
    }
    
}

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Noch eine Möglichkeit:
#include <utility>
#include <type_traits>

struct X {
  X square () const { return {}; }
};

template <typename T>
constexpr std::enable_if_t<std::is_floating_point<T>::value, T> square (const T& a) {
  return a * a;
}

template <typename T>
constexpr decltype (std::declval<const T> ().square ()) square (const T& a) {
  return a.square ();
}

int main () {
  square (X {});
  square (42.);
//  square (5);  // Fehler
}
Über SFINAE werden die einzelnen Overloads aktiviert. Hat den Vorteil, 
dass man beliebig weitere Overloads für andere Arten von Typen (z.B. 
Integer, Klassen die kein .square() aber operator* haben, ...) 
hinzufügen kann, solange die Auswahl immer eindeutig ist. Dies wird 
möglich weil die zweite Variante auf die Existenz von .square() prüft, 
anstatt einfach alle nicht-float-Typen zu akzeptieren.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Noch einfacher gehts mit constraints (ab gcc-6.2):

template<typename T>
requires std::is_floating_point<T>::value
T squareX(const T& x) {
    return x * x;
}

A squareX(const A& x) {
    return x.square();
}

Autor: Vincent Hamp (vinci)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> Über SFINAE werden die einzelnen Overloads aktiviert. Hat den Vorteil,
> dass man beliebig weitere Overloads für andere Arten von Typen (z.B.
> Integer, Klassen die kein .square() aber operator* haben, ...)
> hinzufügen kann, solange die Auswahl immer eindeutig ist. Dies wird
> möglich weil die zweite Variante auf die Existenz von .square() prüft,
> anstatt einfach alle nicht-float-Typen zu akzeptieren.

Und man braucht keinen C++17 Compiler, sollte das ein Kriterium sein.
"Schöner" is imho trotzdem die von Willhelm gepostete Variante.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Vincent H. schrieb:
> Dr. Sommer schrieb:
>> Über SFINAE werden die einzelnen Overloads aktiviert. Hat den Vorteil,
>> dass man beliebig weitere Overloads für andere Arten von Typen (z.B.
>> Integer, Klassen die kein .square() aber operator* haben, ...)
>> hinzufügen kann, solange die Auswahl immer eindeutig ist. Dies wird
>> möglich weil die zweite Variante auf die Existenz von .square() prüft,
>> anstatt einfach alle nicht-float-Typen zu akzeptieren.
>
> Und man braucht keinen C++17 Compiler, sollte das ein Kriterium sein.
> "Schöner" is imho trotzdem die von Willhelm gepostete Variante.

Wir haben Ende 2017: welchen Grund sollte es geben, kein C++17 zu 
verwenden?

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vincent H. schrieb:
> "Schöner" is imho trotzdem die von Willhelm gepostete Variante.
Wenn sie so ausreichend ist, ja. C++ Programmierer mögen Overloads, weil 
man so für alle möglichen Typen unterschiedliches Verhalten definieren 
kann anhand relativ komplexer Kriterien, und außerdem noch später 
zusätzliche Varianten hinzufügen kann ohne die Original-Funktion zu 
ändern. So passiert das auch z.B. in der Standard-Library mit std::hash 
und std::begin.

Wilhelm M. schrieb:
> Wir haben Ende 2017: welchen Grund sollte es geben, kein C++17 zu
> verwenden?
z.B. der GCC-ARM-Embedded (offizieller GCC von ARM) kann C++17 nur 
teilweise, und eben kein "if constexpr"...

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> Noch eine Möglichkeit:
> [c]#include <utility>
> #include <type_traits>
>
> struct X {
>   X square () const { return {}; }
> };
>
> template <typename T>
> constexpr std::enable_if_t<std::is_floating_point<T>::value, T> square
> (const T& a) {
>   return a * a;
> }
>
> template <typename T>
> constexpr decltype (std::declval<const T> ().square ()) square (const T&
> a) {
>   return a.square ();
> }


Statt decltype(...) kann man ainfach auto für den Typ der Funktion 
schreiben.

constexpr nutzt nichts, da oben die Elementfunktion X::square() nicht 
constexpr ist (-> kein constexpr-Kontext).

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> Vincent H. schrieb:
>> "Schöner" is imho trotzdem die von Willhelm gepostete Variante.
> Wenn sie so ausreichend ist, ja. C++ Programmierer mögen Overloads, weil
> man so für alle möglichen Typen unterschiedliches Verhalten definieren
> kann anhand relativ komplexer Kriterien, und außerdem noch später
> zusätzliche Varianten hinzufügen kann ohne die Original-Funktion zu
> ändern. So passiert das auch z.B. in der Standard-Library mit std::hash
> und std::begin.
>
> Wilhelm M. schrieb:
>> Wir haben Ende 2017: welchen Grund sollte es geben, kein C++17 zu
>> verwenden?
> z.B. der GCC-ARM-Embedded (offizieller GCC von ARM) kann C++17 nur
> teilweise, und eben kein "if constexpr"...

Wir reden in diesem Unter-Forum über not-embedded, oder?

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> Vincent H. schrieb:
>> "Schöner" is imho trotzdem die von Willhelm gepostete Variante.
> Wenn sie so ausreichend ist, ja. C++ Programmierer mögen Overloads, weil

oder eben template-Spezialisierungen ...

SFINAE, um eine Funktion aus dem Overload-Set zu entfernen, gehört 
eigentlich der Vergangenheit an ... auch wenn concepts noch nicht 
generell verfügbar sind, sollte man sich darauf einstellen. Mit C++20 
haben wir dieses Mega-Feature!

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Statt decltype(...) kann man ainfach auto für den Typ der Funktion
> schreiben.
Aber dann trifft der Overload auf alle Typen zu, auch z.B. integer, und 
führt dann beim Aufruf zum Fehler. Somit wird ein zusätzlicher Overload 
für Integer unmöglich gemacht. Das decltype(...) stellt hier sicher, 
dass T eine Klasse ist und .square() kennt.

Wilhelm M. schrieb:
> Wir reden in diesem Unter-Forum über not-embedded, oder?
Es könnte ja sein dass jemand so etwas auch auf Controllern nutzen will, 
da spricht ja nichts gegen.

Wilhelm M. schrieb:
> constexpr nutzt nichts, da oben die Elementfunktion X::square() nicht
> constexpr ist (-> kein constexpr-Kontext).
X::square ist nicht constexpr, aber eine später hinzugefügte Klasse Y 
könnte das als constexpr haben. Dafür ist es sinnvoll vorzusorgen.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> oder eben template-Spezialisierungen ...
Sind aber etwas lästiger, weil man hier noch eine extra-Hilfsfunktion 
braucht die die Unterscheidung macht, man kann nicht direkt SFINAE-mäßig 
unterscheiden.

Wilhelm M. schrieb:
> auch wenn concepts noch nicht
> generell verfügbar sind, sollte man sich darauf einstellen. Mit C++20
> haben wir dieses Mega-Feature!
Ja, sag Bescheid wenn es standardisiert und compiler-übergreifend 
verfügbar ist.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> Wilhelm M. schrieb:
>> Statt decltype(...) kann man ainfach auto für den Typ der Funktion
>> schreiben.
> Aber dann trifft der Overload auf alle Typen zu, auch z.B. integer, und
> führt dann beim Aufruf zum Fehler. Somit wird ein zusätzlicher Overload
> für Integer unmöglich gemacht. Das decltype(...) stellt hier sicher,
> dass T eine Klasse ist und .square() kennt.

Ja, das stimmt. Sorry!

> Wilhelm M. schrieb:
>> Wir reden in diesem Unter-Forum über not-embedded, oder?
> Es könnte ja sein dass jemand so etwas auch auf Controllern nutzen will,
> da spricht ja nichts gegen.

Genau, z.B. auch AVR, wo es geht.

Autor: lalala (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Wir haben Ende 2017: welchen Grund sollte es geben, kein C++17 zu
> verwenden?

Einige Gründe:

- Es gibt wohl derzeit noch keinen Compiler, der C++17 vollständig 
unterstützt: http://en.cppreference.com/w/cpp/compiler_support
- Einige Compiler sind sehr weit von einer auch nur halben Unterstützung 
entfernt
- Wer immer auf die aktuellste Software umsteigt, ist zwar ein löblicher 
beta-tester, aber wird kommerziell scheitern
- Inkompatible ABIs. Wenn ich z.B. meine C++ Objekte in Python (mit z.B. 
swig) einbinden möchte, muss ich den Compiler nehmen, mit dem die Python 
Version compiliert wurde. Cpython 3.6 wurde mit MSVC 2015 compiliert.
- Selbes Problem für alle C++ Bibliotheken.

Autor: lalala (Gast)
Datum:

Bewertung
-2 lesenswert
nicht lesenswert
Noch als Anmerkung: Ich denke, dass bald der Stand erreicht ist, das man 
von C++11 als universellen Standard ausgehen kann. C++17 sehe ich so ab 
2025.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
lalala schrieb:
> Noch als Anmerkung: Ich denke, dass bald der Stand erreicht ist, das man
> von C++11 als universellen Standard ausgehen kann. C++17 sehe ich so ab
> 2025.

Das sehe ich ganz und gar nicht so! Aber Prognosen sind immer schwierig, 
vor allem, wenn sie die Zukunft betreffen ;-)

Autor: lalala (Gast)
Datum:

Bewertung
-2 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Das sehe ich ganz und gar nicht so!

Du denkst, das dauert noch länger?

Oder Du denkst wir sind schon da? Dann erzähl mal, welche C++17 
Bibliotheken Du verwendest, und wo Du Kunden (außer im 
Insolvenzverzeichnis :-)) findest, die glücklich wären, wenn Du C++17 
Code/Bibliotheken auslieferst.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dazu solltest Du einen neuen Thread aufmachen ...

Autor: Sjarne Boustrup (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Wir haben Ende 2017: welchen Grund sollte es geben, kein C++17 zu
> verwenden?

Ähm, C++17 ist erst in der DIS-Stage und stellt noch gar nicht "den" 
aktuellen offiziellen Standard dar (das ist momentan C++14).

Autor: lalala (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Dazu solltest Du einen neuen Thread aufmachen ..

aber gerne doch.
Beitrag "Welchen C++ Standard"

Autor: nicht"Gast" (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Warum nicht ganz Klassisch so?
template<class T>
T square(T &value) {  
  return value.square();
}

template<>
float square<float>(float &value) {  
  return value*value;
}

Also, ich kann auch komplett falsch liegen. Euer Code sieht 
komplizierter aus^^

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
nicht"Gast" schrieb:
> Warum nicht ganz Klassisch so?

Übergib mal ein "double" oder einen "int"... Außerdem wäre es schlau die 
Referenzen "const" zu machen.

Autor: nicht"Gast" (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> nicht"Gast" schrieb:
>> Warum nicht ganz Klassisch so?
>
> Übergib mal ein "double" oder einen "int"... Außerdem wäre es schlau die
> Referenzen "const" zu machen.

Jetz hab ichs. Ohne farbigen Code ist der Text vom TE aber auch echt 
schwer zu lesen. :)

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sjarne Boustrup schrieb:
> Wilhelm M. schrieb:
>> Wir haben Ende 2017: welchen Grund sollte es geben, kein C++17 zu
>> verwenden?
>
> Ähm, C++17 ist erst in der DIS-Stage und stellt noch gar nicht "den"
> aktuellen offiziellen Standard dar (das ist momentan C++14).

Allerdings gab es keine Einwände zum DIS:

https://herbsutter.com/2017/09/06/c17-is-formally-approved/

so dass nur noch redaktionelle Änderungen vor der eigentlichen 
Veröffentlichung als ISO Std. notwendig sind. Also: das was wir momentan 
haben IST schon inhaltlich / technisch der offizielle Standard. Und die 
Arbeit an C++20 hat schon vor einiger Zeit begonnen.

Autor: tictactoe (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Für C++17 ist definitiv die Version von Wilhelm M. am besten. Solltest 
do noch unter C++11 oder C++14 unterwegs sein, wäre das hier eine 
Möglichkeit:
#include <type_traits>

template<typename T>
inline T squareXX(const T &x, std::false_type)
{
    return x.square();
}

template<typename T>
inline T squareXX(const T &x, std::true_type)
{
    return x * x;
}

template<typename T>
inline T squareX(const T &x)
{
    return squareXX<T>(x, std::is_floating_point<T>{});
}

double testSquareX(double x)
{
    return squareX<double> (x);
}
Mit -O2 wird daraus
        mulsd   %xmm0, %xmm0
        ret
so wie es sein soll.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.