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


von Johann L. (gjlayde) Benutzerseite


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():


1
#include <type_traits>
2
3
template<typename T>
4
inline T squareX (const T &x)
5
{
6
    return squareXX<T, std::is_floating_point<T>::value> (x);
7
}
8
9
double testSquare (double x)
10
{
11
    return squareX<double> (x);
12
}

squareX ist ein Wrapper, der abhängig von der Klassifizierung von T 
entweder squareXX<T,true> oder squareXX<T,false> verwenden soll.
1
template<typename T, bool B>
2
static inline T squareXX (const T &x);
3
4
template<typename T>
5
inline T squareXX<false> (const T &x)
6
{
7
    return x.square();
8
}
9
10
template<typename T>
11
inline T squareXX<true> (const T &x)
12
{
13
    return x * x;
14
}
1
x.cpp:180:37: error: template-id 'squareXX<false>' in declaration of primary template
2
 inline T squareXX<false> (const T &x)
3
                                     ^
4
x.cpp:186:36: error: template-id 'squareXX<true>' in declaration of primary template
5
 inline T squareXX<true> (const T &x) { return x * x; }
6
                                    ^
7
x.cpp:186:10: error: redefinition of 'template<class T> T squareXX(const T&)'
8
 inline T squareXX<true> (const T &x) { return x * x; }
9
          ^
10
x.cpp:180:10: note: 'template<class T> T squareXX(const T&)' previously declared here
11
 inline T squareXX<false> (const T &x)
12
          ^
13
x.cpp:174:17: warning: inline function 'T squareXX(const T&) [with T = double; bool B = true]' used but never defined
14
 static inline T squareXX (const T &x);/*
15
                 ^
16
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?

von Wilhelm M. (wimalopaan)


Lesenswert?

Theoretisch so:
1
template<typename T, bool B>
2
static inline T squareXX (const T &x);
3
4
template<typename T>
5
inline T squareXX<T, false> (const T &x)
6
{
7
    return x.square();
8
}
9
10
template<typename T>
11
inline T squareXX<T, true> (const T &x)
12
{
13
    return x * x;
14
}

ABER: Du kannst Funktionen nicht partiell spezialisieren.

von Wilhelm M. (wimalopaan)


Lesenswert?

So gehts:
1
template<typename T>
2
T squareX (const T &x)
3
{
4
    if constexpr(std::is_floating_point<T>::value) {
5
        return x * x;        
6
    }
7
    else {
8
        return x.square();
9
    }
10
    
11
}

von Dr. Sommer (Gast)


Lesenswert?

Noch eine Möglichkeit:
1
#include <utility>
2
#include <type_traits>
3
4
struct X {
5
  X square () const { return {}; }
6
};
7
8
template <typename T>
9
constexpr std::enable_if_t<std::is_floating_point<T>::value, T> square (const T& a) {
10
  return a * a;
11
}
12
13
template <typename T>
14
constexpr decltype (std::declval<const T> ().square ()) square (const T& a) {
15
  return a.square ();
16
}
17
18
int main () {
19
  square (X {});
20
  square (42.);
21
//  square (5);  // Fehler
22
}
Ü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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Noch einfacher gehts mit constraints (ab gcc-6.2):
1
template<typename T>
2
requires std::is_floating_point<T>::value
3
T squareX(const T& x) {
4
    return x * x;
5
}
6
7
A squareX(const A& x) {
8
    return x.square();
9
}

von Vincent H. (vinci)


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.

von Wilhelm M. (wimalopaan)


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?

von Dr. Sommer (Gast)


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"...

von Wilhelm M. (wimalopaan)


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).

von Wilhelm M. (wimalopaan)


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?

von Wilhelm M. (wimalopaan)


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!

von Dr. Sommer (Gast)


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.

von Dr. Sommer (Gast)


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.

von Wilhelm M. (wimalopaan)


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.

von lalala (Gast)


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.

von lalala (Gast)


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.

von Wilhelm M. (wimalopaan)


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 ;-)

von lalala (Gast)


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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Dazu solltest Du einen neuen Thread aufmachen ...

von Sjarne Boustrup (Gast)


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).

von lalala (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Dazu solltest Du einen neuen Thread aufmachen ..

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

von nicht"Gast" (Gast)


Lesenswert?

Warum nicht ganz Klassisch so?
1
template<class T>
2
T square(T &value) {  
3
  return value.square();
4
}
5
6
template<>
7
float square<float>(float &value) {  
8
  return value*value;
9
}

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

von Dr. Sommer (Gast)


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.

von nicht"Gast" (Gast)


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. :)

von Wilhelm M. (wimalopaan)


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.

von tictactoe (Gast)


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:
1
#include <type_traits>
2
3
template<typename T>
4
inline T squareXX(const T &x, std::false_type)
5
{
6
    return x.square();
7
}
8
9
template<typename T>
10
inline T squareXX(const T &x, std::true_type)
11
{
12
    return x * x;
13
}
14
15
template<typename T>
16
inline T squareX(const T &x)
17
{
18
    return squareXX<T>(x, std::is_floating_point<T>{});
19
}
20
21
double testSquareX(double x)
22
{
23
    return squareX<double> (x);
24
}
Mit -O2 wird daraus
1
        mulsd   %xmm0, %xmm0
2
        ret
so wie es sein soll.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.