Forum: PC-Programmierung C++ ist schon eine verrückte Sprache


von Stefan (Gast)


Lesenswert?

Ich hatte gerade etwas über Referenzen nachgelesen -- und es dann zur 
Sicherheit noch mal ausprobiert. Ist tatsächlich so wie ich es in der 
Erklärung verstanden hatte -- aber schon recht sonderbar.
1
#include <iostream>
2
int main()
3
{
4
  int i = 7;
5
  float x = 7.0;
6
  const int &ri = i;
7
  const int &rx = x;
8
  const int &ri2 = 2 * i;
9
10
  i = 8;
11
  x = 8.0;
12
  
13
  std::cout << ri << std::endl;
14
  std::cout << rx << std::endl;
15
  std::cout << ri2 << std::endl;
16
  return 0;
17
}
1
$ ./a.out 
2
8
3
7
4
14

von Sven B. (scummos)


Lesenswert?

"mit auto wär das nicht passiert"

von mh (Gast)


Lesenswert?

Was ist daran jetzt verrückt oder sonderbar?

von Marc H. (marchorby)


Lesenswert?

mh schrieb:
> Was ist daran jetzt verrückt oder sonderbar?

das er es verstanden hat! :-) (eventuell ist er ein Spätzünder und das 
ist wahrscheinlich ihm bekannt)

von Dr. Sommer (Gast)


Lesenswert?

Dass konstante Referenzen auf temporäre Werte erlaubt sind, und die 
Referenz die Lebensdauer des Werts verlängert. "rx" ist keine Referenz 
auf x (geht auch gar nicht weil inkompatible Typen), sondern eine auf 
eine "unsichtbare" float-Variable, die einmal mit dem konvertierten Wert 
von x initialisiert, aber dann nicht mehr geändert wird.

von Stefan (Gast)


Lesenswert?

Ja, so erklärt es der Lippman auch. Gut, muss man sich halt merken.

von Da D. (dieter)


Lesenswert?

Dr. Sommer schrieb:
> sondern eine auf
> eine "unsichtbare" float-Variable,

Du meinst sicher int Variable?

von Dr. Sommer (Gast)


Lesenswert?

Da D. schrieb:
> Du meinst sicher int Variable?
Öhm ja, natürlich.

Beitrag #5313705 wurde von einem Moderator gelöscht.
Beitrag #5313708 wurde von einem Moderator gelöscht.
Beitrag #5313714 wurde von einem Moderator gelöscht.
Beitrag #5313715 wurde von einem Moderator gelöscht.
Beitrag #5313717 wurde von einem Moderator gelöscht.
Beitrag #5313730 wurde von einem Moderator gelöscht.
von c-hater (Gast)


Lesenswert?

mh schrieb:

> Was ist daran jetzt verrückt oder sonderbar?

Nun, es spricht nicht gerade für eine Sprache, wenn nicht das 
herauskommt, was man beim ersten Betrachten des Quelltextes ganz 
natürlich erwarten würde.

Solche Sachverhalte zeigen i.d.R., das eine Sprache sinnlos aufgeblasen 
ist, bis hin zur potentiellen Unbrauchbarkeit(*).

(*) Das ist der Punkt, wenn es aufwendiger wird, die Sprache zu 
kapieren, als das Problem, was man damit lösen möchte. Dieser Punkt war 
bei C++ im Moment der Sprachschöpfung bereits in sehr vielen Fällen 
erreicht und sogar weit überschritten. Heute ist C++ reine 
Klartextverschlüsselung. Eine typische Write-only-Sprache. Es ist 
mittlerweile fast unmöglich geworden, aus dem Quelltext auf das damit 
angeblich gelöste Problem zu schliessen, selbst wenn man C++ beherrscht 
(was allerdings mittlerweile auch fast niemand mehr tatsächlich von sich 
behaupten kann)...

von Adapter (Gast)


Lesenswert?

c-hater schrieb:
> mh schrieb:
>
>> Was ist daran jetzt verrückt oder sonderbar?
>
> Nun, es spricht nicht gerade für eine Sprache, wenn nicht das
> herauskommt, was man beim ersten Betrachten des Quelltextes ganz
> natürlich erwarten würde.
>

Das ist aber ein komplett selbst verschuldetes Problem.

Ich codiere seit >> 10 Jahren in C++, aber das ganze Gedöns aus ersten 
Beitrag in diesem thread interessiert mich nicht die Bohne. Kann auch 
(muss nicht) für Embedded komplett irrelevant sein. Ein stdout (oder 
anderer stream) ist ein künstliches Konstrukt, das mit der Sprache 
erstmal herzlich wenig zu tun hat.

Eines der Riesenvorteile von C++ ist die Möglichkeit der Einkapselung. 
Denke bei C++ an eine Hyperstrukt, die neben den (Daten)Strukturmembers 
auch Funktionen haben kann, die implizit Alles was die Instanz der 
Strukt hat mit referenziert. Also statt in C zu schreiben

Fn1(MeineStrukt *abc)
{
   ...abc->member1...
   ...
   abc->member2...
}

und

Fn2(MeineStrukt *abc)
{
   ...abc->member1...
   ...
   abc->member2...
}

schreibst Du in C++:

class MeineStrukt
{
    private <typegal> member1,member2;
    ...
    public:
    Fn1(void);
    Fn2(void);
}

MeineStrukt::Fn1()
{
   member1...
   ...
   member2...
}

MeineStrukt::Fn2()
{
   member1...
   ...
   member2...
}

Selbst (gerade) wenn Du den gesamten C++ Schnickschnak wie überladene 
Operatoren, Copy Konstruktoren, templates, SEH etc. weglässt, wird 
dieser Code gegenüber C um Millionenfach les- und wartbarer. Und kostet 
praktisch kein einziges Byte mehr footprint.

Der im TO skizzierte Code ist vielleicht für PC Programmierer 
interessant. Für Embedded gehört er zu den Klickibunti Gimmicks von C++, 
die man genau so gut weglassen kann und ohne die derselbe Code gegenüber 
C von einem wirren Wust zu richtig gut wird.

von Student (Gast)


Lesenswert?

c-hater schrieb:
> Nun, es spricht nicht gerade für eine Sprache, wenn nicht das
> herauskommt, was man beim ersten Betrachten des Quelltextes ganz
> natürlich erwarten würde.

Wo ist das denn hier der Fall?

von Dr. Sommer (Gast)


Lesenswert?

Adapter schrieb:
> Der im TO skizzierte Code ist vielleicht für PC Programmierer
> interessant. Für Embedded gehört er zu den Klickibunti Gimmicks von C++,
> die man genau so gut weglassen kann

Referenzen sind PC Klickibunti Zeug? Die Ausgabe per std::cout hat 
überhaupt nichts mit dem Problem zu tun. Würdest du per printf() 
ausgeben oder die Zahlen direkt in irgendwelche Register schreiben, wäre 
das "Problem" das gleiche.

Außerdem ist OOP nur ein kleiner Teil von C++. Da gibts noch viel mehr 
was gerade für Embedded gut ist...

von Adapter (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Adapter schrieb:
>> Der im TO skizzierte Code ist vielleicht für PC Programmierer
>> interessant. Für Embedded gehört er zu den Klickibunti Gimmicks von C++,
>> die man genau so gut weglassen kann
>
> Referenzen sind PC Klickibunti Zeug? Die Ausgabe per std::cout hat
> überhaupt nichts mit dem Problem zu tun. Würdest du per printf()
> ausgeben oder die Zahlen direkt in irgendwelche Register schreiben, wäre
> das "Problem" das gleiche.
>

Konstrukte wie std::cout haben für das, was ICH in Embedded mache, 
keinerlei Relevanz. Genau so wenig wie prontf().

> Außerdem ist OOP nur ein kleiner Teil von C++. Da gibts noch viel mehr
> was gerade für Embedded gut ist...

Beispiele?

Es ging mir darum, die im ursprünglichen Beitrag (gebetsmühlenartig) 
reiterierte Kritik von C++ als nicht mehr echtzeitles- und verstehbare 
Sprache zu relativieren und zu zeigen, dass (zumindestens für Leute aus 
der C-Welt) eine Untermenge von C++, die wirklich nichts von diesen 
Konstrukten wie << (das ist für C Programmierer ein Left shift, was 
z.Teufel soll das mit Ausgaben zu tun haben) nur Vorteile bringt.

von Dr. Sommer (Gast)


Lesenswert?

Adapter schrieb:
> Konstrukte wie std::cout haben für das, was ICH in Embedded mache,
> keinerlei Relevanz. Genau so wenig wie prontf().

Kann sein. Hat aber nix mit dem Ausgangsposting zu tun. Ersetze das 
"std::cout<<" durch irgendeine andere Form der Ausgabe. Auch 
Embedded-Programme müssen irgendwie mit der Außenwelt kommunizieren, und 
sei es über GPIO-Pins.

Adapter schrieb:
> Beispiele?
Insbesondere Template-Metaprogrammierung und constant expressions. 
Weniger für Embedded, aber dennoch mächtig wären Exceptions 
(OOP-verwandt).

Adapter schrieb:
> Es ging mir darum, die im ursprünglichen Beitrag (gebetsmühlenartig)
> reiterierte Kritik
Die kann man einfach ignorieren.

Adapter schrieb:
> die wirklich nichts von diesen
> Konstrukten wie << (das ist für C Programmierer ein Left shift, was
> z.Teufel soll das mit Ausgaben zu tun haben) nur Vorteile bringt.
Der << Operator ist vielleicht unglücklich gewählt. Die Grundidee der 
typsicheren Ausgabe ist aber ein sehr gutes Konzept und printf() 
überlegen. Die C++ Syntax ist ohne Frage schrecklich (dank des Erbes von 
C), darüber braucht man nicht zu diskutieren, sondern kann sich der 
darunterliegenden Semantik widmen.

von Sven B. (scummos)


Lesenswert?

Adapter schrieb:
>> Außerdem ist OOP nur ein kleiner Teil von C++. Da gibts noch viel mehr
>> was gerade für Embedded gut ist...
>
> Beispiele?

Templates?

von Vincent H. (vinci)


Lesenswert?

Ja C++ erlaubt einem schon manch obskure Dinge. =)

Ich hab heute etwa folgendes verbrochen
1
    return []() -> R { assert(false); }();

um den Return-Type einer Funktion zu befriedigen (R entspricht dem zu 
retunierenden Typen).

: Bearbeitet durch User
von Adapter (Gast)


Lesenswert?

Sven B. schrieb:
> Adapter schrieb:
>>> Außerdem ist OOP nur ein kleiner Teil von C++. Da gibts noch viel mehr
>>> was gerade für Embedded gut ist...
>>
>> Beispiele?
>
> Templates?

Hab ich noch nie gebraucht (was aber nichts heissen muss).

von Dr. Sommer (Gast)


Lesenswert?

Vincent H. schrieb:
> Ich hab heute etwa folgendes verbrochen

Und was wird da zurückgegeben wenn NDEBUG definiert ist? Wenn das nie 
der Fall ist, warum rufst du dann nicht abort() auf, oder wirfst eine 
Exception?

Adapter schrieb:
> Hab ich noch nie gebraucht (was aber nichts heissen muss).

Die kann man für alles und jedes gebrauchen. Ganz einfaches Beispiel 
sind die Funktionen std::min/max, welches das Minimum/Maximum der 
übergebenen Zahlen zurückgeben. Sowas geht in C gar nicht (ohne 
Seiteneffekte doppelt auszuführen) und in C++ nur sinnvoll mit 
templates.

von Vincent H. (vinci)


Lesenswert?

Dr. Sommer schrieb:
> Vincent H. schrieb:
>> Ich hab heute etwa folgendes verbrochen
>
> Und was wird da zurückgegeben wenn NDEBUG definiert ist? Wenn das nie
> der Fall ist, warum rufst du dann nicht abort() auf, oder wirfst eine
> Exception?

Exceptions gibts nicht und abort() würde an jener Stelle im Release 
einen Sprung einbaun den ich nicht brauch.

von Dr. Sommer (Gast)


Lesenswert?

Vincent H. schrieb:
> Exceptions gibts nicht und abort() würde an jener Stelle im Release
> einen Sprung einbaun den ich nicht brauch.

Und was passiert dann im Release? Rückgabe eines nicht existenten Werts, 
d.h. undefined behaviour?

von Vincent H. (vinci)


Lesenswert?

Sollte jemand die (bestens dokumentierte) Funktion mit Werten aufrufen 
die das ermöglich würden, dann ja. Das ist aber dann nicht mein 
Kaffee...

von Dr. Sommer (Gast)


Lesenswert?

Okay. __builtin_unreachable könnte auch helfen, ist aber 
Compiler-Spezifisch. Und ob eine unnötige Sprung-Anweisung im 
Programmspeicher jetzt so schlimm ist...

von Johannes S. (Gast)


Lesenswert?

der Thread hat interessant angefangen, langsam geht es aber wieder in 
das übliche C++ bashing hier über :( Und wenn Moby den morgen früh 
findet is eh vorbei...

Das einzige was mich an dem Beispiel vom TO stört ist das der Compile 
keine Warnung wirft bei der impliziten Typkonvertierung in 'const int 
&rx = x;', jedenfalls hat mein gcc das mit den gerade aktuellen 
Einstellungen nicht gemacht. Ich habe das mal meinem µC ins Flash 
geworfen und er kommt zum selben Ergebniss wie der TO. Gut, der Code 
wurde um 230 kB grösser, aber was soll der Geiz wenn der STM32F407 512 
kB hat? Irgendwie muss man die ja mal testen :-)

von Sven B. (scummos)


Lesenswert?

Vincent H. schrieb:
> Ja C++ erlaubt einem schon manch obskure Dinge. =)
>
> Ich hab heute etwa folgendes verbrochen
>
1
>     return []() -> R { assert(false); }();
2
>
>
> um den Return-Type einer Funktion zu befriedigen (R entspricht dem zu
> retunierenden Typen).

Hä? Was soll das darstellen? Das sollte nichtmal kompilieren, weil du 
kein return in dem Lambda hast ... das typische Konstrukt ist "return 
{};".

von Yalu X. (yalu) (Moderator)


Lesenswert?

Johannes S. schrieb:
> Das einzige was mich an dem Beispiel vom TO stört ist das der Compile
> keine Warnung wirft bei der impliziten Typkonvertierung in 'const int
> &rx = x;', jedenfalls hat mein gcc das mit den gerade aktuellen
> Einstellungen nicht gemacht.

Stefans Beispiel ist etwas künstlich, aber es gibt Fälle, wo bei der
Erstellung einer Referenz die implizite Typkonvertierung und das
automatische Anlegen eines temporären Objekts des neuen Types durchaus
erwünscht ist, bspw. hier:

1
void func(const std::string &s) {
2
  // mach was mit s
3
}
4
5
int main() {
6
  std::string str;
7
8
  // fülle str mit Inhalt
9
10
  func(str);   // (1)
11
12
  func("ein Stringliteral"); // (2)
13
}

Das Argument s von func ist als Referenz deklariert, damit beim Aufruf
mit einer std::string-Variable (1) diese nicht kopiert werden muss.

Beim Aufruf mit einem Stringliteral (2) passt jedoch der Datentyp nicht.
Durch die implizite Typkonvertierung in ein temporäres Objekt vom Typ
std::string funktioniert der Aufruf trotzdem. Gäbe es weder implizite
Typkonvertierungen noch automatische temporäre Objekte, müsste explizit
und umständlich eine Variable als "Überbringer" des Strings definiert
werden.

von mh (Gast)


Lesenswert?

Sven B. schrieb:
> Hä? Was soll das darstellen? Das sollte nichtmal kompilieren, weil du
> kein return in dem Lambda hast ... ...

Doch das sollte kompilieren. Der Standard sagt, es ist undefined 
behaviour, wenn eine Funktion ohne richtigen Rückgabewert zurückkehrt. 
Wenn sie nie zurückkehrt ist alles in Ordnung.

von Johannes S. (Gast)


Lesenswert?

Wenn wir beim Thema Referenzen sind hätte ich noch eine Frage: Ich habe 
ein Chart Objekt als übergeordnetes, das kann dann PlotAreas erstellen 
(Factory). Liefere ich diese besser als Zeiger oder als RefObjekt 
zurück? Die Idee ist die nicht wieder kaputtzumachen (kein delete), 
deshalb eher Referenzen?
Ich hatte das erst mit Zeigern gemacht, aber mit Referenzen klappt es 
auch:
1
    // basic chart object
2
    Chart chart (tft, 0, 0, 320, 200, WHITE);
3
4
    // create plot area
5
    PlotArea& plotArea = chart.addPlotArea (6 * 6, 0, 0, 10,
6
                                            tft.color565 (128, 128, 128));

habe schon länger nix mehr mit C++ gemacht, freue mich aber das man es 
jetzt so gut auf µCs nutzen kann.

von Dr. Sommer (Gast)


Lesenswert?

Kommt drauf an, wer das PlotArea-Objekt wieder freigibt. Macht die 
"Chart"-Klasse das? Dann ist das in Ordnung so. Macht die Chart-Klasse 
das, aber verwaltet die PlotArea-Objekte intern in einem std::vector 
(welcher die auch freigeben würde)? Dann geht weder Pointer noch 
Referenz, sondern z.B. ein Index.
Soll der Aufrufer das PlotArea freigeben? Dann wäre z.B. std::unique_ptr 
sinnvoll, notfalls ein normaler Pointer, keinesfalls eine Referenz.

von Johannes S. (Gast)


Lesenswert?

weil es mehrere PlotAreas per Chart werden sollen wollte ich das noch in 
einen vector packen, der kann intern aber doch Pointer auf die PlotAreas 
halten? Ob die überhaupt deleted werden sollen wäre auch zu überlegen, 
viel Dynamik möchte ich ja auf dem µC nicht, für eine GUI kann man die 
besser auf unsichtbar schalten wenn die nicht mehr dargestellt werden 
sollen.

von Dr. Sommer (Gast)


Lesenswert?

Johannes S. schrieb:
> weil es mehrere PlotAreas per Chart werden sollen wollte ich das noch in
> einen vector packen, der kann intern aber doch Pointer auf die PlotAreas
> halten?
Wenn der vector aus Pointern besteht ist's in Ordnung. Wenn es ein 
vector<PlotArea> ist werden die Pointer ungültig wenn der vector 
verändert wird. Eventuell ist es sinnvoll vector<unique_ptr<PlotArea>> 
zu nehmen, und dann normale Pointer nach draußen zu geben.

Johannes S. schrieb:
> Ob die überhaupt deleted werden sollen wäre auch zu überlegen,
> viel Dynamik möchte ich ja auf dem µC nicht,
Steht denn zum Zeitpunkt des Kompilierens die Anzahl fest? Dann kannst 
du die auch einmalig statisch alle allokieren. Freigeben ist sinnvoll 
wenn du später weniger brauchst, aber den Speicher für irgendwas anderes 
gebrauchen kannst.

von Johannes S. (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Eventuell ist es sinnvoll vector<unique_ptr<PlotArea>>
> zu nehmen, und dann normale Pointer nach draußen zu geben.

ja, ich sehe schon, ich muss mir die std lib nochmal genauer ansehen, 
mit den unigue_ptr wird es am Besten sein.

Dr. Sommer schrieb:
> Steht denn zum Zeitpunkt des Kompilierens die Anzahl fest?

ich gehe da in kleinen Schritten voran und sehe mir an was das 'kostet'. 
Ein kleines Array mit fixer Anzahl ist vielleicht effizienter, aber so 
Sachen mag ich nicht wo man hinterher ellenlange Konfigs mit Konstanten 
hat. Und ein Index zu weit geschrieben ewige Fehlersuche bedeuten kann.

Danke schonmal für die Tipps, wenn es sich lohnt stelle ich den Code 
hier rein.

von Michael B. (laberkopp)


Lesenswert?

Vincent H. schrieb:
> Ich hab heute etwa folgendes verbrochen    return []() -> R {
> assert(false); }();

Was soll das bringen ?
Die Debug-Fassung des Programm stürzt mit einer assertion failure ab,
die Release Version liefert irgendein Return-Wert, je welchen denn, an 
den Aufrufer ?

http://www.cplusplus.com/reference/cassert/assert/

von Dr. Sommer (Gast)


Lesenswert?

Johannes S. schrieb:
> Ein kleines Array mit fixer Anzahl ist vielleicht effizienter

Kannst ja die Anzahl per Template-Parameter übergeben. Oder das 
Chart-Objekt hat nur einen Pointer auf das Array, welches der Benutzer 
selbst (statisch) allokiert.

von Sven B. (scummos)


Lesenswert?

mh schrieb:
> Sven B. schrieb:
>> Hä? Was soll das darstellen? Das sollte nichtmal kompilieren, weil du
>> kein return in dem Lambda hast ... ...
>
> Doch das sollte kompilieren. Der Standard sagt, es ist undefined
> behaviour, wenn eine Funktion ohne richtigen Rückgabewert zurückkehrt.
> Wenn sie nie zurückkehrt ist alles in Ordnung.

Ja naja, aber wo ist jetzt der Vorteil gegenüber return {}?

von Dr. Sommer (Gast)


Lesenswert?

Sven B. schrieb:
> Ja naja, aber wo ist jetzt der Vorteil gegenüber return {}?

return {} könnte einen Konstruktor aufrufen was noch mehr 
Programmspeicher als abort() fressen könnte oder es könnte kein 
Standard-Konstruktor existieren.

von temp (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Der << Operator ist vielleicht unglücklich gewählt. Die Grundidee der
> typsicheren Ausgabe ist aber ein sehr gutes Konzept und printf()
> überlegen.

Zur Ehrenrettung von printf und Konsorten sei mal angemerkt, dass 
aktuelle Compiler auch hier den Syntax prüfen und die meisten Fehler 
auch bemerken.

Für mich ist der << Operator einer der Sachen, die ich in C++ nie Nutze 
und deren Nutzen ich auch nicht nachvollziehen kann. Man kann beim Lesen 
selbst einer einfachen Ausgabezeile nicht mehr erkennen was überhaupt 
ausgegeben wird.
1
stdout << a << b << c;
Zuerst muss man halt nachsehen ob in a, b und c die "<<" Operatoren 
überladen wurden und was sie daraus machen. Man blättert dann erst mal 
durch mehrere Dateien. Und wenn dann noch jeder Programmierer denkt nur 
er hat denn Schuss gehört und seine eigenen Kreationen z.B. zum Zahlen 
Formatieren sind die besten, dann ist das unübersichtliche Chaos 
perfekt. Da ist mir ein einfaches printf() aber 1000 mal lieber und ich 
lasse mich gern in dieses Korsett zwängen. Niemand würde es z.B. gut 
finden, wenn die Formatanweisungen von printf() in jedem Programm anders 
definiert wären. Das ist aber genau die negative Konsequenz dieser 
Freiheit in C++.

Das ganze wird dann noch durch die Metaprogrammiererei mit Templates 
getoppt. Es gibt sicherlich einige Programmierer die das gut können, die 
meisten sind aber nur der Meinung dass sie das gut können. Es gibt 
genügend Beispiele hier im Forum wo aus trivialen Aufgabenstellungen 
unleserliche C++ Romane entstanden sind. Von der wunderbaren 
Debugbarkeit von Templateorgien will ich gar nicht erst reden.

Programmierer sind ja auch immer Eitel. Und aus dieser Eitelkeit heraus 
versuchen sich auch manche zu beweisen. Da wird dann jedes Problem auf 
eine Art und Weise gelöst, die man nur selbst versteht. Das vermittelt 
dann das überragende Gefühl besser zu sein als die Anderen...

von Sven B. (scummos)


Lesenswert?

Dr. Sommer schrieb:
> Sven B. schrieb:
>> Ja naja, aber wo ist jetzt der Vorteil gegenüber return {}?
>
> return {} könnte einen Konstruktor aufrufen was noch mehr
> Programmspeicher als abort() fressen könnte
?
Der Code macht eh keinen Sinn wenn der jemals aufgerufen wird ...

> oder es könnte kein
> Standard-Konstruktor existieren.
Dann ist die Lösung delcval<R>() und nicht dieser komische Kram.

von Vincent H. (vinci)


Lesenswert?

Sven B. schrieb:
> Dann ist die Lösung delcval<R>() und nicht dieser komische Kram.

Das dacht ich zuerst auch, aber bei der libstdc++ Implementierung kommt 
dann der __declval_protector und meint "you shall not pass" ;)
1
  template<typename _Tp>
2
    struct __declval_protector
3
    {
4
      static const bool __stop = false;
5
      static typename add_rvalue_reference<_Tp>::type __delegate();
6
    };
7
8
  template<typename _Tp>
9
    inline typename add_rvalue_reference<_Tp>::type
10
    declval() noexcept
11
    {
12
      static_assert(__declval_protector<_Tp>::__stop,
13
        "declval() must not be used!");
14
      return __declval_protector<_Tp>::__delegate();
15
    }

von mh (Gast)


Lesenswert?

temp schrieb:
> Für mich ist der << Operator einer der Sachen, die ich in C++ nie Nutze
> und deren Nutzen ich auch nicht nachvollziehen kann. Man kann beim Lesen
> selbst einer einfachen Ausgabezeile nicht mehr erkennen was überhaupt
> ausgegeben wird.

Das kann ich nachvollziehen. Ich selbst nutze "iostream" nur bei sehr 
sehr einfachen Sachen. Zum Beispiel wenn ich zum debuggen eine Variable 
ausgebe.

Bei allem was komplizierter ist und wo es tatsächlich auf die 
Formatierung ankommt benutze ich Alternativen, von denen es ja zum Glück 
genug gibt.

Mein Favorit ist die fmt Bibliothek (http://fmtlib.net/latest/). Die 
kann alles was ich brauche und unterstützt neben der printf auch die 
python.format Syntax. Und evtl. wird sie auch irgendwann in den c++ 
Standard integriert, zumindest ein Proposal gibt es.

von mh (Gast)


Lesenswert?

Vincent H. schrieb:
> Sven B. schrieb:
>> Dann ist die Lösung delcval<R>() und nicht dieser komische Kram.
>
> Das dacht ich zuerst auch, aber bei der libstdc++ Implementierung kommt
> dann der __declval_protector und meint "you shall not pass" ;)

Wie erwartet ist die Implementierung korrekt. Der Standard sagt:
1
23.2.7 Function template declval [declval]
2
1 The library provides the function template declval to simplify the definition of expressions which occur as unevaluated operands (Clause 8).
3
template <class T> add_rvalue_reference_t<T> declval() noexcept;
4
// as unevaluated operand
5
2 Remarks: If this function is odr-used (6.2), the program is ill-formed.
6
3 Remarks: The template parameter T of declval may be an incomplete type.
7
4 [ Example:
8
template <class To, class From> decltype(static_cast<To>(declval<From>())) convert(From&&);
9
declares a function template convert which only participates in overloading if the type From can be explicitly converted to type To. For another example see class template common_type (23.15.7.6). — end example ]

Wichtig ist Remark 2.

Der Standard (C++17 Draft n4687 6.2.3) sagt zu odr-used functions:
1
... A function whose name appears as a potentially-evaluated expression is odr-used if it is the unique lookup result or the selected member of a set of overloaded functions (6.4, 16.3, 16.4), unless it is a pure virtual function and either its name is not explicitly qualified or the expression forms a pointer to member (8.3.1). [ Note: This covers calls to named functions (8.2.2), operator overloading (Clause 16), user-defined conversions (15.3.2), allocation functions for placement new-expressions (8.3.4), as well as
2
non-default initialization (11.6). A constructor selected to copy or move an object of class type is odr-used even if the call is actually elided by the implementation (15.8). — end note ] ...

von Dr. Sommer (Gast)


Lesenswert?

temp schrieb:
> Zur Ehrenrettung von printf und Konsorten sei mal angemerkt, dass
> aktuelle Compiler auch hier den Syntax prüfen und die meisten Fehler
> auch bemerken.

Es sei denn man baut sich eigene Varianten dafür... Selbst beim 
Durchleiten mit variadischen templates funktioniert beim GCC diese 
Prüfung nicht mehr.

temp schrieb:
> Man kann beim Lesen
> selbst einer einfachen Ausgabezeile nicht mehr erkennen was überhaupt
> ausgegeben wird.
Ich finde das super. Wenn ich einen Datentyp ausgeben will, schreib ich 
"<<" hin und fertig. Ich muss mir nicht erst überlegen welche Funktionen 
dafür aufgerufen werden müssen.
Ein Beispiel:
1
#include <iostream>
2
#include <vector>
3
4
struct Point {
5
  int x, y;
6
};
7
8
std::ostream& operator << (std::ostream& os, const Point& p) {
9
  return os << "Point {" << p.x << ", " << p.y << "}";
10
}
11
12
template <typename T, typename Alloc>
13
std::ostream& operator << (std::ostream& os, const std::vector<T, Alloc>& v) {
14
  os << "{";
15
  if (!v.empty ()) {
16
    os << v.front ();
17
    for (std::size_t i = 1; i < v.size (); ++i) {
18
      os << ", " << v [i];
19
    }
20
  }
21
  return os << "}";
22
}
23
24
int main () {
25
  int i = 42;
26
  float f = 3.14;
27
  Point p { 3, 4 };
28
  
29
  std::vector<int> v1 { 1, 2, 3 };
30
  std::vector<Point> v2 { { 2, 3}, { 4, 5 }, { 6, 7} };
31
  
32
  std::cout << i << " " << f << " " << p << " " << v1 << " " << v2 << std::endl;
33
}
Der "<<" Operator wird für Point überladen, jetzt kann ich das direkt 
ausgeben. Wenn ich jetzt eine template-Klasse habe (hier: 
std::vector<T>), kann ich die enthaltenen Elemente genauso ausgeben ohne 
mir Gedanken zu machen wie bestimmte Typen jetzt ausgegeben werden 
sollen. Das wird hier genutzt um beliebige Vektoren ausgeben zu können. 
Das wäre ohne Überladen des "<<" Operators doch ziemlich umständlich. In 
der main()-Funktion kann ich dann Vektoren von int genauso ausgeben wie 
Vektoren von Point. So etwas ist super für verschachtelte generische 
Datenstrukturen.

temp schrieb:
> Zuerst muss man halt nachsehen ob in a, b und c die "<<" Operatoren
> überladen wurden und was sie daraus machen. Man blättert dann erst mal
> durch mehrere Dateien.
Das ist der Sinn von Abstraktion. Details werden verpackt, und nur 
wenn man die genau wissen will schaut man in der Implementation nach. 
Die meisten höheren Programmiersprachen nutzen das ausführlich. Wenn du 
das nicht willst, solltest du alles in Assembler programmieren und 
natürlich niemals Funktionen benutzen. Bei printf() siehst du ja auch 
nicht wie die Ausgabe jetzt genau durchgeführt wird. Du weißt es 
höchstens dank der Dokumentation von printf() - aber eigene "<<" 
Überladungen darf man auch dokumentieren.

temp schrieb:
> Das ist aber genau die negative Konsequenz dieser
> Freiheit in C++.
Man kann sich auch eigene Zahlen-Ausgaben in C mit puts() basteln. Die 
Nutzung der IO-Streams hat definitiv nichts damit zu tun ob man sich 
eigene Zahlenformate baut. Ich hab jedenfalls noch nie gesehen dass das 
jemand tun würde.

In C muss ich immer Angst haben dass die Format-Specifier von printf() 
falsch sind, sobald man den Typ der auszugebenden Variable ändert (z.B. 
time_t könnte sich von 32 auf 64bit ändern), und dann der Code nicht 
mehr funktioniert. Das kann bei den IO-Streams nicht passieren. Und ja, 
die IO-Streams bieten genau so flexible Möglichkeiten zur Formatierung 
wie printf().
Der einzige wirkliche Nachteil der IO-Streams ist dass die Strings 
zerpflückt werden, was schlecht für Übersetzungen ist.

Da ist sowas eine Lösung, was ja - genau wie IO-Streams - ebenfalls 
typsicher ist (und ebenfalls erweitert werden kann, nicht über "<<" aber 
über format_arg, mit einem ähnlichen Ergebnis):
mh schrieb:
> Mein Favorit ist die fmt Bibliothek (http://fmtlib.net/latest/).

von mh (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Da ist sowas eine Lösung, was ja - genau wie IO-Streams - ebenfalls
> typsicher ist (und ebenfalls erweitert werden kann, nicht über "<<" aber
> über format_arg, mit einem ähnlichen Ergebnis):
> mh schrieb:
>> Mein Favorit ist die fmt Bibliothek (http://fmtlib.net/latest/).

fmt kann auch mit ostream und operator<< für user-defined types 
erweitert werden: http://fmtlib.net/latest/api.html#std-ostream-support
Es kann also für alle Typen die schon einen operator<< für die 
std::streams haben direkt genutzt werden. Nur für Typen die eine 
parameterisierbare Formatierung benötigen muss man sich um format_arg 
kümmern.

von Dr. Sommer (Gast)


Lesenswert?

Oh gut zu wissen, das ist natürlich noch besser. Hatte es nur 
überflogen.

von temp (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Es sei denn man baut sich eigene Varianten dafür... Selbst beim
> Durchleiten mit variadischen templates funktioniert beim GCC diese
> Prüfung nicht mehr.

Zeit für die selbsternannten C++ Gurus auch mal wieder etwas über C zu 
lernen:
https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Function-Attributes.html
Ein einfaches Attribut sagt dem gcc dass er diese Funktion im printf 
Style überprüfen soll.
1
extern int my_printf (void *my_object, const char *my_format, ...)
2
                         __attribute__ ((format (printf, 2, 3)));

Dein Beispiel:
1
  std::cout << i << " " << f << " " << p << " " << v1 << " " << v2 << std::endl;

ist so oder ähnlich seit 30 Jahren in jedem C++ Buch zu finden und ein 
hervorragendes Beispiel für lesbaren Code... Und ich für meinen Teil 
hasse das seit genau so vielen Jahren. Du kannst es ja gut finden, das 
ändert aber nichts an meiner Meinung. Ich hab die Erfahrung gemacht, 
gepuscht wird das vor allem von Dozenten die sich an so was wie einen 
"Lehrplan" halten.

von Dr. Sommer (Gast)


Lesenswert?

temp schrieb:
> Zeit für die selbsternannten C++ Gurus auch mal wieder etwas über C zu
> lernen:

Das Attribut kenne ich, und es ist eine Compiler-Erweiterung. Eine 
elegante Lösung ist das nicht gerade, sie basiert darauf dass der 
Compiler printf() und seine Funktionsweise kennt. Ein Compiler sollte 
normalerweise keine einzelnen Funktionen direkt unterstützen - so etwas 
sollte von innerhalb der Sprache möglich sein. Wenn man eigene 
Format-Specifier nachrüsten möchte (z.B. für eigene Datentypen) klappt 
das schon gar nicht mehr.

temp schrieb:
> ist so oder ähnlich seit 30 Jahren in jedem C++ Buch zu finden und ein
> hervorragendes Beispiel für lesbaren Code...
Wie gesagt, das "<<" ist fragwürdig. Die grundlegende Idee, Overloads zu 
nutzen, finde ich äußerst sinnvoll. Bei Arduino z.B. heißt es statt "<<" 
halt "print", mit ähnlicher Semantik. Findest du das dann besser?

temp schrieb:
> Ich hab die Erfahrung gemacht,
> gepuscht wird das vor allem von Dozenten die sich an so was wie einen
> "Lehrplan" halten.
Vielleicht kann man von Dozenten ja noch was lernen, dafür sind die 
schließlich da. Wer möchte dass der Code "hübsch" aussieht, sollte kein 
C++ nutzen. Es ist nicht schön, aber flexibler und sicherer als 
printf().

von Dumdi D. (dumdidum)


Lesenswert?

temp schrieb:
> n einfaches Attribut sagt dem gcc dass er diese Funktion im printf Style
> überprüfen soll.

Wie geht das im Visualstudio?

von apr (Gast)


Lesenswert?

Dumdi D. schrieb:
> Wie geht das im Visualstudio?

https://msdn.microsoft.com/en-us/library/ms235402(v=VS.100).aspx

Also so in der Richtung (mehr als nur ungetested):
1
extern int my_printf (void *my_object, _Printf_format_string_ *my_format, ...);

von Johannes S. (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Bei Arduino z.B. heißt es statt "<<"
> halt "print", mit ähnlicher Semantik. Findest du das dann besser?

Bei Arduino bestimmt aber die Print Klasse die Formattierung, beim << 
Operator wird die vom auszugebenden Objekt erstellt. Das Print müsste 
man um die neuen Typen erweitern, finde ich nicht schön.

Bezieht ihr euch bei euren iostream Beispielen auf PC oder µC? Ich hatte 
ja gestern das auf dem µC eingebunden und der Code wuchs um +230 kB 
(Debug Release mit newlib). Das erscheint mir sehr viel, ist diese 
Grössenordnung normal?

von Dr. Sommer (Gast)


Lesenswert?

Johannes S. schrieb:
> Bei Arduino bestimmt aber die Print Klasse die Formattierung,

Nur bei vordefinierten Typen - genau wie bei io streams.

Johannes S. schrieb:
> Das Print müsste man um die neuen Typen erweitern, finde ich nicht
> schön.

Einfach einen Overload hinzufügen, genau wie bei io streams. Das ist 
absolut gängiges Vorgehen in C++, dafür gibt es ja auch ADL. Hier rächt 
sich aber das schlechte Design der Arduino Library, nämlich dass die 
Print Funktionen in einer Klasse sind...

Johannes S. schrieb:
> Bezieht ihr euch bei euren iostream Beispielen auf PC oder µC

PC. Die gängigen Implementationen der IOStreams sind nicht uC geeignet.

von temp (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Vielleicht kann man von Dozenten ja noch was lernen, dafür sind die
> schließlich da.

Wenn Dozenten ein simples Beispiel aufschreiben wie du:
1
int main () {
2
  int i = 42;
3
  float f = 3.14;
4
  Point p { 3, 4 };
5
  
6
  std::vector<int> v1 { 1, 2, 3 };
7
  std::vector<Point> v2 { { 2, 3}, { 4, 5 }, { 6, 7} };
8
  
9
  std::cout << i << " " << f << " " << p << " " << v1 << " " << v2 << std::endl;
10
}

Da wird ohne Not einfach ein vector benutzt der dynamischen Speicher 
belegt. Wir sind hier in einem µC Forum, also da wo es noch darum geht 
mit den Resourcen zu knausern. Die Anfänger übernehmen dass und 
programmieren in Zukunft in dem Stil weiter ohne darüber Nachzudenken 
was etwas kostet. Leider kann man auch kaum noch abschätzen was am Ende 
ein Template kostet. Deshalb macht sich auch niemand mehr Gedanken 
darüber, dafür werden die Programme immer fetter und damit auch nicht 
sicherer.

Dr. Sommer schrieb:
> Bei Arduino z.B. heißt es statt "<<"
> halt "print", mit ähnlicher Semantik. Findest du das dann besser?

Nein, Arduino ist ein ganz schlechtes Beispiel. Gerade weil es da in der 
Lib auch ziemlich inkonsequent gehandhabt wird. Wenn man da versucht 
z.B. auf dem ESP8622 ein simples Internetprotokoll (IMAP, MAIL o.ä.) mit 
Ausgaben zu Debuggen weil's anders nicht geht, kriegt man die Motten. 
Die Libs und der Core verwenden schon gemischt "\r\n" und "\n" als 
Zeilenende. Die println-Funktion nimmt std::endl. Die IMAP oder SMTP 
Strings wollen "\r\n" am Zeilenende. Am Ende ist die Ausgabe im Terminal 
immer irgendwie zerrupft.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

temp schrieb:
> Wir sind hier in einem µC Forum, also da wo es noch darum geht mit den
> Resourcen zu knausern.

Wir sind im Unterforum "PC-Programmierung" ;-)

von Dr. Sommer (Gast)


Angehängte Dateien:

Lesenswert?

temp schrieb:
> Da wird ohne Not einfach ein vector benutzt der dynamischen Speicher
> belegt.
Gah, das war ein Beispiel. Wenn man nur genau diese Ausgabe erreichen 
will, kann man auch
1
std::fputs("42 3.14 Point {3, 4} {1, 2, 3} {Point {2, 3}, Point {4, 5}, Point {6, 7}}", stdout);
schreiben. Manchmal hat man halt Vektoren, und das wäre eine Möglichkeit 
sie flexibel auszugeben. Tausche std::vector gegen std::array und du 
hast das gleiche in Nicht-Dynamisch.

temp schrieb:
> Leider kann man auch kaum noch abschätzen was am Ende
> ein Template kostet.
Genau so viel wie entsprechender C-Code.

temp schrieb:
> Nein, Arduino ist ein ganz schlechtes Beispiel.
Okay, lassen wir Arduino, machen wir das mal so wie im Anhang gezeigt - 
eine einfache Bibliothek zur Ausgabe von Integern und Strings. Der 
Einfachheit halber geben wir die Zahlen per printf() aus. In echt würde 
man da einen entsprechenden Algorithmus einbauen.
Bei Variante 1 haben wir Funktions-Overloads namens "print" ähnlich zu 
Arduino. Bei Variante 1.5 werden die noch etwas kompakter mit einer 
variadischen Funktion aufgerufen.
Bei Variante 2 wird wie bei IO-Streams der "<<" Operator überladen. In 
allen Versionen kann man jetzt problemlos Overloads für eigene Klassen, 
hier "Point", hinzufügen und alles über ein einheitliches Interface 
ausgeben. Welche Variante findest du jetzt besser?

von Yalu X. (yalu) (Moderator)


Lesenswert?

Die I/O-Streams in C++ sind manchmal auf Grund ihrer Erweiterbarkeit
sehr praktisch. So kann man bspw. in der OpenCV eine Matrix ganz einfach
mit

1
  std::cout << matrix;

ausgeben.

Der absoluten Horror ist es aber, damit gewöhnliche Zahlenwerte als
sauber formatierte Tabelle auszugeben:

- Das kleinste Problem ist dabei noch der <<-Operator, der zu sperrig
  ist, nicht nur vor jeder auszugebenden Variable, sondern zusätzlich
  vor jedem Formatierungselement benötigt wird, einen Missbrauch des
  Bitshift-Operators darstellt und deswegen auch die falsche Präzedenz
  hat.

- Die Syntax ist insgesamt an Umständlichkeit und Unübersichtlichkeit
  kaum zu überbieten.

- Manche der Formatierungsparameter gelten nur jeweils für das nächste
  auszugebende Element (z.B. std::setw) und müssen deswegen u.U. laufend
  wiederholt werden.

- Andere (z.B. std::setprecision) sind persistent und müssen nach
  erfolgter Nutzung explizit rückgängig gemacht werden, um nicht andere
  Ausgaben im Programm zu beeinflussen.

Hier zwei Beispiele:

1
#include <iostream>
2
#include <iomanip>
3
#include <cstdio>
4
5
int main() {
6
7
  // Beispiel 1, Tabellezeile mit nummerierten dreidimesionalen Koordinaten
8
9
  int n =16;
10
  double x=23.2345, y=456.5732, z=-2.3456;
11
12
  std::printf("%3d %9.3f %9.3f %9.3f\n", n, x, y, z);
13
14
  std::cout << std::setw(3) << n << std::fixed << std::setprecision(3) << ' '
15
    << std::setw(9) << x << ' ' << std::setw(9) << y << ' ' << std::setw(9)
16
    << z << '\n' << std::setprecision(6) << std::defaultfloat;
17
18
19
  // Beispiel 2, Hexdumpzeile mit Adress und Low-/High-Byte
20
21
  unsigned int addr = 0x123abc;
22
  unsigned char lbyte = 0x23, hbyte=0x0a;
23
24
  printf("%08X: %02X %02X\n", addr, lbyte, hbyte);
25
26
  std::cout << std::hex << std::uppercase << std::setfill('0') << std::setw(8)
27
    << addr << ": " << std::setw(2) << +lbyte << ' ' << std::setw(2) << +hbyte
28
    << '\n' << std::setfill(' ') << std::nouppercase << std::dec;
29
}

Ausgabe:

1
 16    23.235   456.573    -2.346
2
 16    23.235   456.573    -2.346
3
00123ABC: 23 0A
4
00123ABC: 23 0A

Die Printf-Varianten sind nicht nur sehr viel kürzer, man sieht auch auf
einen Blick, welche Variablen überhaupt ausgegeben werden. Bei etwas
genauerer Betrachtung des Formatstrings kann man sich sogar vorstellen,
wie die ausgegebene Tabellenzeile ungefähr aussehen wird. Das alles geht
in der cout-Variante völlig unter.

Deswegen benutze ich I/O-Streams nur dann, wenn ich wirklich auf ihre
Erweiterbarkeit angewiesen bin. In den 95% aller Fälle, wo dies nicht
der Fall ist, ist printf für mich ganz klar die bessere Alterenative.

Beitrag #5314682 wurde von einem Moderator gelöscht.
von temp (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Welche Variante findest du jetzt besser?

Ziel ist es nicht eine Aufgabe zu finden für die deine Lösung besser 
passt.

Yalu X. schrieb:
> Der absoluten Horror ist es aber, damit gewöhnliche Zahlenwerte als
> sauber formatierte Tabelle auszugeben:

Dem und den ganzen anderen was du geschrieben hast kann ich voll 
zustimmen.
Es geht auch nicht darum die Ausgabe über Streams in C++ komplett zu 
verteufeln. Umgedreht kann ich aber nicht akzeptieren, dass es nur noch 
über diesen Weg gehen muss und allen, die das nicht so sehen, Ignoranz, 
Dummheit oder Lernfaulheit unterstellt wird.

von Dr. Sommer (Gast)


Lesenswert?

temp schrieb:
> Ziel ist es nicht eine Aufgabe zu finden für die deine Lösung besser
> passt.

Das ist nicht "meine" Lösung, und ich wollte nur versuchen 
herauszufinden, was genau dein Problem damit ist.

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.