C++ Template-Spezialisierung?

OP Persönliche Seite #5181752
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?
#5181774
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.
Gast #5181785
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.
#5181795
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.
#5181801
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?
Gast #5181803
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"...
#5181805
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).
#5181809
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?
#5181811
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!
Gast #5181813
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.
Gast #5181817
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.
#5181820
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.
Gast #5181837
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.
Gast #5181858
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.
Gast #5181923
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^^
#5181986
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.
Gast #5182242
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren