Forum: Mikrocontroller und Digitale Elektronik c: Wie multiple return-Statements vermeiden?


von Mike (Gast)


Lesenswert?

Ich habe eine c-Funktion, in der verschiedene Alternativen von 
Berechnungsmethoden getestet werden sollen. Die Berechnungen sind als 
c-Funktion implementiert und geben bei Erfolg einen Wert != 0 zurück. 
Bei Nichterfolg soll die nächste Methode probiert werden. usw.

Bisher ist das ganze so programmiert:
1
unsigned int check()
2
{
3
  unsigned int result;
4
5
  result = func1();
6
  if (result != 0) 
7
     return result;
8
9
  result = func2();
10
  if (result != 0) 
11
     return result;
12
13
  result = func3();
14
  if (result != 0) 
15
     return result;
16
17
  ... // viele weitere
18
19
  return 0;  //keine Methode erfolgreich
20
}

Nun gelten mehrfache returns bekanntlich als schlechter Programmierstil. 
Wie kann man das vermeiden, ohne an Übersichtlichkeit zu verlieren? Eine 
Unzahl von verschachtelten if/else Abfragen mit entsprechend vielen 
geschweiften Klammern ist meiner Meinung nach keine Alternative.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Mike schrieb:
> Nun gelten mehrfache returns bekanntlich als schlechter Programmierstil.
> Wie kann man das vermeiden, ohne an Übersichtlichkeit zu verlieren? Eine

 Vielleicht auf Null abfragen, anstatt auf != 0.

von Jan H. (j_hansen)


Lesenswert?

Mike schrieb:
> Nun gelten mehrfache returns bekanntlich als schlechter Programmierstil.

Das ist sicher keine allgemeingültige Meinung.

Mike schrieb:
> Wie kann man das vermeiden, ohne an Übersichtlichkeit zu verlieren?

Schwierig, viel übersichtlicher wird es nicht werden, und das ist eines 
der wichtigen Ziele in der Programmierung.

Mike schrieb:
> Eine
> Unzahl von verschachtelten if/else Abfragen mit entsprechend vielen
> geschweiften Klammern ist meiner Meinung nach keine Alternative.

Du könntest über die Möglichkeiten loopen.

von Dumdi D. (dumdidum)


Lesenswert?

Ein array von funktionspointern übergeben und durch das array mit einer 
for schleife. Dann gibt es nur einen ausstiegspunkt (mit break oder auch 
mit return)

von Einer K. (Gast)


Lesenswert?

Mike schrieb:
> Eine
> Unzahl von verschachtelten if/else Abfragen mit entsprechend vielen
> geschweiften Klammern ist meiner Meinung nach keine Alternative.

Es gibt ja auch noch Goto.
Oder eine Verschachtelung des trinären Operators.

Grundsätzlich gilt ja wohl:
Wenn du die Verzweigungen brauchst, dann brauchst du sie.

Wenn dir multiple Return nicht schmecken, musst du anders dahin 
verzweigen.
Es bieten sich da else oder Goto an.

Dumdi D. schrieb:
> array von funktionspointern
Wenn es besser "schmeckt", als mehrfache return, dann ja.

von fab_v (Gast)


Lesenswert?

Am einfachsten lesbar evtl weiterhin mit if-Abfragen.
1
unsigned int check()
2
{
3
  unsigned int result = 0;  //Wert falls kein Erfolg
4
5
  result = func1();
6
7
  if (result == 0) result = func2();
8
9
  if (result == 0) result = func3();
10
11
  if (result == 0) ...;
12
13
14
  return result;  //keine Methode erfolgreich
15
}

von Karl (Gast)


Lesenswert?

Mike schrieb:
> Nun gelten mehrfache returns bekanntlich als schlechter Programmierstil.

Was immer wieder Gegenstand von Diskussionen ist. So allgemeingültig 
finde ich ist das nicht. Wie Du selbst erkannt hast, endet es sonst in 
einem Wust an if-else if-else. Maximale Verschachtelungstiefe wird von 
diversen Coding-Standards auch beschränkt. Goto ist auch pöhse.

"Lösung": Unterfunktionen, damit werden alle Metriken erfüllt. Ist doch 
egal, dass das nur noch dämlicher wird, weil das eigentliche Problem das 
Du zu lösen hast nun mal so ist wie es ist.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Alternativer Ansatz:
1
unsigned int check()
2
{
3
  unsigned int result = 0;
4
5
  result = func1();
6
7
  if (!result)
8
    result = func2();
9
10
  if (!result)
11
    result = func3();
12
13
  ... // viele weitere
14
15
  return result;
16
}

Allerdings sehe ich die Annahme "Nun gelten mehrfache returns 
bekanntlich als schlechter Programmierstil" als durchaus zwiespältig an.

Bestimmte Konstrukte sind mit mehrfachen Returns klar, kurz und 
übersichtlich.

Manche Konstrukte werden durch krampfhaftes Vermeiden mehrfacher Returns 
sogar ausgesprochen unübersichtlich.

von Karl (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Dumdi D. schrieb:
>> array von funktionspointern
> Wenn es besser "schmeckt", als mehrfache return, dann ja.

Ernsthaft? Oder sollte das witzig sein?

In einer Welt in der der geneigte Programmierer zu doof ist, mehrere 
returns zu verkraften bewerfen wir ihn mit Funktionspointern um ein 
dogmatisches Befolgen von allgemeinen "Weisheiten" zu erreichen? Deine 
Kollegen oder Nachfolger werden es danken.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

1
unsigned int check()
2
{
3
  unsigned int result;
4
5
  if ((result = func1()) == 0 &&
6
      (result = func2()) == 0 &&
7
      (result = func3()) == 0)
8
  {
9
    result = do_something ();
10
  }
11
  return result;
12
}

Vorteile:
- Keine verschachtelten if-Statements
- Die Bedingungen stehen übersichtlich untereinander
- Die Reihenfolge der func()-Aurufe ist wie gewünscht
- Abbruch der func()-Aufrufe, sobald ein Fehler zurückkommt
- Keine Klammer-Orgie
- Nur ein return

: Bearbeitet durch Moderator
von DPA (Gast)


Lesenswert?

Warnung: alles ungetestet.

Haben die Funktionen alle das gleiche Interface? Dann könntest du 
eventuell eine Schleife nehmen:
1
typedef unsigned int (*check_function)(void);
2
3
const check_function check_list[] = {
4
  check_1,
5
  check_2,
6
  check_3,
7
  0
8
};
9
10
unsigned int check(void){
11
  unsigned int result = 0;
12
  for(check_function* f=check_list; *f && !result; f++)
13
    result = f();
14
  return result;
15
}

Hat der Rückgabewert irgendeine Bedeutung abgesehen von OK/Weiter? 
Spielt die Reihenfolge eine rolle? Falls nein, könnte man die Check 
Funktionen sich eventuell sogar gleich selbst registrieren lassen:

check_list.h
1
typedef unsigned int (*check_function)(void);
2
3
struct check_list_entry {
4
  check_function callback;
5
  struct check_list_entry* next;
6
}
7
8
extern struct check_list_entry* check_list;
9
10
#define CONCAT_EVAL(A,B) A ## B
11
#define CONCAT(A,B) CONCAT_EVAL(A,B)
12
13
#define REGISTER_CHECK_FUNCTION(REF) \
14
  static void CONCAT(init_register_check_function_, __LINE__)(void) __attribute__(contructor,used); \
15
  static void CONCAT(init_register_check_function_, __LINE__)(void){ \
16
    static struct check_list_entry entry; \
17
    entry.callback = (REF); \
18
    entry.next = check_list; \
19
    check_list = &entry; \
20
  }

check_list.c
1
#include <check_list.h>
2
3
check_function* check_list;
4
5
unsigned int check(void){
6
  unsigned int result = 0;
7
  for(struct check_list_entry* it=check_list; it && !result; it=it->next)
8
    result = f();
9
  return result;
10
}

check_stuff_1.c
1
#include <check_list.h>
2
3
static unsigned int check_1(void){
4
  return 0;
5
}
6
REGISTER_CHECK_FUNCTION(check_1);
7
8
static unsigned int check_2(void){
9
  return 0;
10
}
11
REGISTER_CHECK_FUNCTION(check_2);

check_stuff_2.c
1
#include <check_list.h>
2
3
static unsigned int check_3(void){
4
  return 0;
5
}
6
REGISTER_CHECK_FUNCTION(check_3);
7
8
static unsigned int check_4(void){
9
  return 0;
10
}
11
REGISTER_CHECK_FUNCTION(check_4);

von DPA (Gast)


Lesenswert?

Edit: Das 2te "result = f();" sollte "result = it->callback();" sein.

von Dirk K. (merciless)


Lesenswert?

Mike schrieb:
> Nun gelten mehrfache returns bekanntlich als schlechter Programmierstil.

Nö. Les mal Clean Code von Robert C. Martin.

Ist das C-Code oder C++? Im letzteren Fall
kann ich mir eine Lösung mit dem Decorator
Pattern vorstellen.

merciless

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Mike schrieb:
> Nun gelten mehrfache returns bekanntlich als schlechter Programmierstil.
> Wie kann man das vermeiden,

Lass es so. Diese Regel wirkt sich nur aus wenn die unterschiedlichen 
returns irgendwo kreuz und quer in verschachtelten Schleifen stecken und 
es mühsam wird zu erkennen ob und wann der Teil darunter nun ausgeführt 
wird weil man sie übersehen kann oder weil sie an Bedingungen geknüpft 
sind für die man sich erstmal das Hirn verrenken muss um sie im Kopf 
durchzugehen.

Bei Deinem Beispiel sieht man auf den ersten Blick was da vor sich geht, 
da ist nichts versteckt oder überraschend, das vorzeitige Return ist das 
Kernthema und der Dreh- und Angelpunkt dieser Funktion, es springt einem 
sofort ins Auge und man kann es nicht übersehen. Also lass es so.

: Bearbeitet durch User
von zitter_ned_aso (Gast)


Lesenswert?

Bei dir sehen alle Funktion von der Signatur her gleich aus.

Da könnnte man mit einem Funktionszeiger arbeiten.
1
    int (*func_ptr[])(void)={func1, func2, func3};

Dann brauchst du nur eine while/for-Schleife um durch sie zu iterieren 
bis das richtige Ergebnis kommt. Und brauchst nicht die ganzen if's.

von Thomas M. (langhaarrocker)


Lesenswert?

Frank M. schrieb:
> unsigned int check()
> {
>   unsigned int result;
>
>   if ((result = func1()) == 0 &&
>       (result = func2()) == 0 &&
>       (result = func3()) == 0)
>   {
>     result = do_something ();
>   }
>   return result;
> }
>
> Vorteile:
> - Keine verschachtelten if-Statements
> - Die Bedingungen stehen übersichtlich untereinander
> - Die Reihenfolge der func()-Aurufe ist wie gewünscht
> - Abbruch der func()-Aufrufe, sobald ein Fehler zurückkommt
> - Keine Klammer-Orgie
> - Nur ein return

Ich finde es fürchterlich, weil ich eine Zuweisung innerhalb einer 
Abfrage für sowas wie das Ausnützen eines Seiteneffekts halte. Beim 
flüchtigen Schnell-drüber-lesen ist die Zuweisung ratzfatz überlesen. 
Schlimmer noch: ein Nachfolgeprogrammierer könnte die Zuweisung sogar 
für einen Flüchtigkeitsfehler halten und ihn "korrigieren" wollen. 
Hirnknötchen völlig ohne Not, da wären multiple returns ganz sicher 
vorzuziehen.

Normalerweise quittieren viele Compiler Zuweisungen innerhalb einer 
Condition direkt mit einer Warnung.

von Falk B. (falk)


Lesenswert?

Thomas M. schrieb:
> Ich finde es fürchterlich, weil ich eine Zuweisung innerhalb einer
> Abfrage für sowas wie das Ausnützen eines Seiteneffekts halte.

Darüber kann man geteilter Meinung sein.

> Beim
> flüchtigen Schnell-drüber-lesen ist die Zuweisung ratzfatz überlesen.

Dann machst du was falsch.

> Schlimmer noch: ein Nachfolgeprogrammierer könnte die Zuweisung sogar
> für einen Flüchtigkeitsfehler halten und ihn "korrigieren" wollen.

Dann ist es kein C-Programmierer.

> Hirnknötchen völlig ohne Not, da wären multiple returns ganz sicher
> vorzuziehen.

Die Reihe der normalen If() ist die saubere Variante, auch für 
empfindliche Gemüter.

Beitrag "Re: c: Wie multiple return-Statements vermeiden?"

> Normalerweise quittieren viele Compiler Zuweisungen innerhalb einer
> Condition direkt mit einer Warnung.

Dann sind es aber keine C-Compiler.

von Thomas M. (langhaarrocker)


Lesenswert?

DPA schrieb:
> Warnung: alles ungetestet.
>
> Haben die Funktionen alle das gleiche Interface? Dann könntest du
> eventuell eine Schleife nehmen:
>
1
 wilder Code Wust

Das ist bestimmt eine nette Programmiersportaufgabe. Aber alleine dass 
ich beim Eingangspost mit einem Blick erkannte, was er wollte und diesen 
Codewust  direkt als tldr; abtat, sagt aus, dass diese Lösung unterlegen 
ist.


P.s.: tldr; = to long, didn't read

von Stefan F. (Gast)


Lesenswert?

Wichtig ist, dass der Quelltext gut lesbar ist. Und das ist beim Code im 
Eröffnungspost der Fall.

Ich würde hier vielleicht noch ein paar Zeilenumbrüche heraus nehmen.

Es gib immer wieder mal Fälle, wo man von den allgemeinen Stil-Vorgaben 
abweichen sollte, um die Lesbarkeit zu verbessern. Man muss nur einen 
nachvollziehbaren Grund nennen können, dann ist das meiner Meinung nach 
schon Ok.

Wenn du später mal im Team programmierst, wirst du schnell merken, dass 
Diskussionen um solche kleinen Details reine Zeitverschwendung sind. Wir 
(in der Firma) diskutieren nur über unverständlichen und fehlerhaften 
Code.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Thomas M. schrieb:
> Ich finde es fürchterlich, weil ich eine Zuweisung innerhalb einer
> Abfrage für sowas wie das Ausnützen eines Seiteneffekts halte.

Das ist ganz normales C. Wenn ich mich recht erinnere, lernt man das bei 
K&R direkt in Kapitel 1:
1
  while ((c = getchar ()) != EOF)
2
  {
3
    putchar (c);
4
  }

Wenn das schon die grauen Hirnzellen überfordert, sollte man es mit C 
besser sein lassen.

> Normalerweise quittieren viele Compiler Zuweisungen innerhalb einer
> Condition direkt mit einer Warnung.

Ja, wenn Du die Klammern um die Zuweisung vergisst - und das zu recht. 
Die habe ich aber nicht vergessen. ;-)

: Bearbeitet durch Moderator
von DPA (Gast)


Lesenswert?

Falk B. schrieb:
>> Normalerweise quittieren viele Compiler Zuweisungen innerhalb einer
>> Condition direkt mit einer Warnung.
>
> Dann sind es aber keine C-Compiler.

gcc und clang können das. In dem beispiel aber nicht. Die zweite () ist 
wie man bei den Compilern die Warnung wieder abstellt, und anzeigt, ja, 
dass soll ne Zuweisung sein.

Thomas M. schrieb:
> Das ist bestimmt eine nette Programmiersportaufgabe. Aber alleine dass
> ich beim Eingangspost mit einem Blick erkannte, was er wollte und diesen
> Codewust  direkt als tldr; abtat, sagt aus, dass diese Lösung unterlegen
> ist.
>
> P.s.: tldr; = to long, didn't read

Das erste beispiel ist nur 15 Zeilen lang, also weniger als das 
Original. Weniger Zeichen waren es auch noch. Nur die 2te Option war 
etwas länger, aber auch nur weil diese die func1, func2, etc. auch noch 
zeigten.

von Thomas M. (langhaarrocker)


Lesenswert?

Arduino Fanboy D. schrieb:
> Es gibt ja auch noch Goto.

Naja - wer schon multiple returns verpönt, was sagt der wohl zum Thema 
goto?

In diesem Kontext (ohne weitere Berechnungen vor dem returm am 
Funktionsende) wäre goto return ja effektiv das selbe wie return, nur 
halt mit anderen Buchstaben geschrieben geschrieben und der werte Leser 
müsste noch Mausrad und Hirn beanspruchen, um das rauszukriegen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

DPA schrieb:
> Falk B. schrieb:
>> Dann sind es aber keine C-Compiler.
>
> gcc und clang können das.

Du hast Falk missverstanden.

Er meinte: Wenn ein C-Compiler das anmeckert, was ich anständig in
1
(result = func1()) == 0
geklammert habe, dann ist es kein C-Compiler. Und da hat Falk vollkommen 
recht.

Thomas M. schrieb das nämlich zu allgemeingültig. Er behauptete, dass 
einige C-Compiler immer bei Zuweisungen innerhalb eines Vergleichs 
meckern. Das stimmt aber nicht. Klammert man sorgfältig, ist das 
vollkommen okay und kein C-Compiler der Welt meckert das an.

: Bearbeitet durch Moderator
von Thomas M. (langhaarrocker)


Lesenswert?

DPA schrieb:
> Das erste beispiel ist nur 15 Zeilen lang, also weniger als das
> Original.
1
typedef unsigned int (*check_function)(void);
2
3
const check_function check_list[] = {
4
  check_1,
5
  check_2,
6
  check_3,
7
  0
8
};
9
10
unsigned int check(void){
11
  unsigned int result = 0;
12
  for(check_function* f=check_list; *f && !result; f++)
13
    result = f();
14
  return result;
15
}

Ehrlich gesagt ... ab einer ausreichend großen Anzahl von check_n 
Funktionen  würde ich es wohl ziemlich genau so machen. :)

Bei n <= 3 würde ich es jedoch ausgerollt lassen.

: Bearbeitet durch User
von Johann J. (johannjohanson)


Lesenswert?

Ich würde es so machen - das ist aus meiner Sicht lesbar(er):
1
unsigned int check()
2
{
3
  unsigned int result;
4
5
  if( ( result = func1() ) == 0)
6
    if( ( result = func2() ) == 0)
7
      if( ( result = func3() ) == 0)
8
  ... // viele weitere
9
        return 0;
10
11
  return result;
12
}

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Rufus Τ. F. schrieb:
> Manche Konstrukte werden durch krampfhaftes Vermeiden mehrfacher Returns
> sogar ausgesprochen unübersichtlich.

Haste grade MISRA gesagt?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Mw E. schrieb:
> Haste grade MISRA gesagt?

Die Kernaussage, warum man vorzeitige Returns in der Regel vermeiden 
sollte:

Werden am Ende der Funktion noch Aufräumaktionen getätigt, läuft man 
Gefahr, diese bei vorzeitigen Returns einfach zu vergessen.

Eine Folge könnten zum Beispiel die allseits bekannten Memory-Leaks 
sein. Existiert jedoch keine solche Aufräumaktion, habe ich auch keine 
Skrupel, ein vorzeitiges Return einzubauen.

: Bearbeitet durch Moderator
von Oliver S. (oliverso)


Lesenswert?

Johann J. schrieb:
> ... // viele weitere

So ab 70 wirds mit den Einrückungen aber etwas unschön ;)

Ich finde das Original ja nach wie vor gut, aber bitte, einen hab ich 
auch noch:
1
  if ((result = func1()) !=0) return result;
2
  if ((result = func2()) !=0) return result;
3
  if ((result = func3()) !=0) return result;
4
  if ((result = func4()) !=0) return result;

Oliver

von Thomas M. (langhaarrocker)


Lesenswert?

Frank M. schrieb:
> Thomas M. schrieb das nämlich zu allgemeingültig. Er behauptete, dass
> einige C-Compiler immer bei Zuweisungen innerhalb eines Vergleichs
> meckern. Das stimmt aber nicht. Klammert man sorgfältig, ist das
> vollkommen okay und kein C-Compiler der Welt meckert das an.

Das mag sein. Aber das ist dann doch auch schon wieder irgendein 
Expertenklugsch***erwissen, von dem ich wetten würde, dass, wenn man mit 
einer Umfrage - selbst routiniertere und gute - Programmierer damit 
konfrontieren würde, es sich herausstellen würde, dass sie es auch nicht 
wussten.


Guter Stoff für eine Klausurfrage, aber im täglichen Einsatz so nutzlos 
wie Schachtelsätze.

von Bernd K. (prof7bit)


Lesenswert?

Frank M. schrieb:
> Werden am Ende der Funktion noch Aufräumaktionen getätigt, läuft man
> Gefahr, diese bei vorzeitigen Returns einfach zu vergessen.

Genau. Und diesen Zustand umschifft man stilgerecht mit goto anstelle 
von return ;-)

Oder man baut ne ne dummy-Schleife und verlässt sie mit break.

von Stefan F. (Gast)


Lesenswert?

Frank M. schrieb:
> Werden am Ende der Funktion noch Aufräumaktionen getätigt, läuft man
> Gefahr, diese bei vorzeitigen Returns einfach zu vergessen.

Dafür wurde in Java try-finally erfunden. Leider hat C/C++ das nicht.

von Stefan F. (Gast)


Lesenswert?

Bernd K. schrieb:
> Genau. Und diesen Zustand umschifft man stilgerecht mit goto anstelle
> von return ;-)

Hier ein konkretes Code-Beispiel:
1
/**
2
 * Perform an I²C transaction, which sends 0 or more data bytes, followed by receiving 0 or more data bytes.
3
 *
4
 * @param registerStruct May be either I2C1, I2C2 or I2C3
5
 * @param slave_addr 7bit slave_addr (will be shifted within this function)
6
 * @param send_buffer Points to the buffer that contains the data bytes that shall be sent (may be 0 if not used)
7
 * @param send_size Number of bytes to send
8
 * @param receive_buffer Points to the buffer that will be filled with the received bytes (may be 0 if not used)
9
 * @param receive_size Number of bytes to receive
10
 * @return Number of received data bytes, or -1 if sending failed
11
 */
12
int i2c_communicate(I2C_TypeDef* registerStruct, uint8_t slave_addr,
13
    void* send_buffer, int send_size, void* receive_buffer, int receive_size)
14
{
15
    int receive_count=-1;
16
17
    // Set slave address (shifted 1 bit to the left)
18
    MODIFY_REG(registerStruct->CR2, I2C_CR2_SADD, slave_addr << (1+I2C_CR2_SADD_Pos));
19
20
    // Send data
21
    if (send_size>0)
22
    {
23
        // Data direction
24
        CLEAR_BIT(registerStruct->CR2, I2C_CR2_RD_WRN);
25
26
        // Configure size of the first data block to send
27
        configureBlockSize(registerStruct, send_size);
28
29
        // Send start condition
30
        SET_BIT(registerStruct->CR2, I2C_CR2_START);
31
32
        // Send data
33
        do
34
        {
35
            // Check for error
36
            if (READ_BIT(registerStruct->ISR, I2C_ISR_NACKF | I2C_ISR_ARLO))
37
            {
38
                goto error;
39
            }
40
41
            // Send one byte when ready
42
            if (READ_BIT(registerStruct->ISR, I2C_ISR_TXIS))
43
            {
44
                WRITE_REG(registerStruct->TXDR, *((uint8_t*)send_buffer));
45
                send_buffer++;
46
                send_size--;
47
            }
48
49
            // Configure size of next block, if requested
50
            if (READ_BIT(registerStruct->ISR, I2C_ISR_TCR))
51
            {
52
                configureBlockSize(registerStruct, send_size);
53
            }
54
        }
55
        // Loop  until the transfer is complete
56
        while (!READ_BIT(registerStruct->ISR, I2C_ISR_TC));
57
    }
58
59
    // Sending succeeded, start counting the received bytes
60
    receive_count=0;
61
62
    // Receive data
63
    if (receive_size>0)
64
    {
65
        // Data direction
66
        SET_BIT(registerStruct->CR2, I2C_CR2_RD_WRN);
67
68
        // Configure size of the first data block to receive
69
        configureBlockSize(registerStruct, receive_size);
70
71
        // Send start or restart condition
72
        SET_BIT(registerStruct->CR2, I2C_CR2_START);
73
74
        // Receive data
75
        do
76
        {
77
            // Check for error
78
            if (READ_BIT(registerStruct->ISR, I2C_ISR_ARLO))
79
            {
80
                goto error;
81
            }
82
83
            // Fetch one received bytes when ready
84
            if (READ_BIT(registerStruct->ISR, I2C_ISR_RXNE))
85
            {
86
                *((uint8_t*)receive_buffer)=READ_REG(registerStruct->RXDR);
87
                receive_buffer++;
88
                receive_count++;
89
                receive_size--;
90
            }
91
92
            // Configure size of next block, if requested
93
            if (READ_BIT(registerStruct->ISR, I2C_ISR_TCR))
94
            {
95
                configureBlockSize(registerStruct, receive_size);
96
            }
97
        }
98
        // Loop  until the transfer is complete
99
        while (!READ_BIT(registerStruct->ISR, I2C_ISR_TC));
100
    }
101
102
    // Send stop condition
103
    SET_BIT(registerStruct->CR2, I2C_CR2_STOP);
104
105
    return receive_count;
106
107
    error:
108
    // Restart the I2C peripheral
109
    CLEAR_BIT(registerStruct->CR1, I2C_CR1_PE);
110
    SET_BIT(registerStruct->CR1, I2C_CR1_PE);
111
    //ITM_SendString("I2C bus error!\n");
112
    return receive_count;
113
}

Ich glaube das ist das einzige Szenario, in dem ich außerhalb des C64 
jemals goto verwendet habe.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Thomas M. schrieb:
> Das mag sein.

Das ist so. Zumindest sollte man den Basis-C-Standard verstanden 
haben.

> Aber das ist dann doch auch schon wieder irgendein
> Expertenklugsch***erwissen,

Wenn schon die C-Bibel von Kernighan & Ritchie als zweites 
Programmierbeispiel
1
  while ((c = getchar()) != EOF)
direkt nach dem Hello-World-Programm aufführt, dann sind das 
Anfänger-Basics und keinesfalls "Expertenklugsch***erwissen", wie Du 
es ausdrückt.

> Guter Stoff für eine Klausurfrage, aber im täglichen Einsatz so nutzlos
> wie Schachtelsätze.

Das ist im täglichen Einsatz. Und Du wirst diese Zuweisungen auch in 
fast jedem Open-Source finden, die etwas größer sind als Dreizeiler.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> Genau. Und diesen Zustand umschifft man stilgerecht mit goto anstelle
> von return ;-)

Hier ein Beispiel aus der Praxis:

  Beitrag "Re: [STM32/HAL] generischer Treiber für die beliebten I²C-Text-Displays"

Zunächst siehst Du da die goto-Orgie, wie W.S. das umgesetzt hat und 
dann von mir die Lösung ohne goto.

Welche ist lesbarer?

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Frank M. schrieb:
> if ((result = func1()) == 0 &&
>       (result = func2()) == 0 &&
>       (result = func3()) == 0)
>   {

&& ?

ich weiss nicht, was all die function pointer orgien hier sollen ...
simple und kurz gehts auch!


uint_t check() {
  uint_t result;

  if (result=func1() || result=func2() || result=func2()) return result;

  return 0;
}


mt

von Stefan F. (Gast)


Lesenswert?

Frank M. schrieb:
> Zunächst siehst Du da die goto-Orgie, wie W.S. das umgesetzt hat

Hmm, wer hat da wohl von wem abgeguckt. Sieht ja fast so aus, wie mein 
Beispiel.

Frank, dein Beispiel sieht schöner aus. Aber wenn die Blöcke etwas 
komplexer werden, wie in meinem obigen i2c Beispiel, dann klappt das 
nicht mehr mit der besseren Lesbarkeit.

Goto ist allgemein zu vermeiden, und mehrere Returns sollte man sich 
auch gut überlegen. Manchmal machen sie aber Sinn.

von Johann J. (johannjohanson)


Lesenswert?

Oliver S. schrieb:
> So ab 70 wirds mit den Einrückungen aber etwas unschön ;)

Noch nie was von "ordentlichen Bildschirmgrößen" gehört?  :-)

Bei solchen Schachtelungstiefen mit 7465 Checks würde ich dann eher so 
vorgehen (dann kann man check() auch gleich weglassen):
1
unsigned int func1()
2
{
3
  unsigned int result;
4
5
  // Schwieriger Check - Ergebnis in result 
6
 
7
  if( result == 0)
8
    result = func2();
9
10
 return result;
11
}
12
  
13
unsigned int func2()
14
{
15
  unsigned int result;
16
17
  // Schwieriger Check - Ergebnis in result 
18
 
19
  if( result == 0)
20
    result = func3();
21
22
 return result;
23
}
24
    
25
... func3, func4, ..., func7464, 
26
27
unsigned int func7465()
28
{
29
  unsigned int result;
30
31
  // Schwieriger Check - Ergebnis in result 
32
 
33
  return result;
34
}
35
36
37
unsigned int check()
38
{
39
  unsigned int result;
40
41
  result = func1();
42
 
43
  return result;
44
}

von Einer K. (Gast)


Lesenswert?

Stefanus F. schrieb:
> Ich glaube das ist das einzige Szenario, in dem ich außerhalb des C64
> jemals goto verwendet habe.

Mit Goto kann man schöne und einfache endliche Automaten bauen.
Switch/case ist da nicht unbedingt so effektiv.


---

Bei diesem Problem hier ist es echt egal, ob man per goto zu einem 
zentralen Ausstieg hüpft, den Code mit Dutzenden Return pflastert, oder 
eine if/else Kaskade aufbaut.

Einen Schönheitswettbewerb wird man damit sowieso nicht gewinnen

Rechteckig, hat er zu sein, der Code.
Symmetrisch soll er sein. (Kunst für Doofe)
1
unsigned int check1()
2
{
3
  unsigned int result;
4
  
5
  if(result = func1()) return result;
6
  if(result = func2()) return result;
7
  if(result = func3()) return result;
8
  return result;
9
}
10
11
12
unsigned int check2()
13
{
14
  unsigned int result;
15
  
16
  if(result = func1()) goto ende;
17
  if(result = func2()) goto ende;
18
  if(result = func3()) goto ende;
19
  
20
  ende:
21
  return result;
22
}

von Stefan F. (Gast)


Lesenswert?

Johann J. schrieb:
> Bei solchen Schachtelungstiefen mit 7465 Checks würde ich dann eher so
> vorgehen

Und prompt einen Stack-Überlauf bekommen.

von Johann J. (johannjohanson)


Lesenswert?

Stefanus F. schrieb:
> Und prompt einen Stack-Überlauf bekommen.

Wurde hier eine konkrete Architektur angesprochen?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Stefanus F. schrieb:
> Hmm, wer hat da wohl von wem abgeguckt. Sieht ja fast so aus, wie mein
> Beispiel.

Ich weiß nicht, was Du jetzt andeuten willst.

Ich habe lediglich den Code von W.S. konkret in dem genannten Beitrag 
rausgepickt und ad hoc umgeschrieben. Selbst verwende ich weder diesen 
Code noch Deinen. Mein I2C-Code, den ich für STM32 verwende und unter 
GPL veröffentlicht ist, sieht gänzlich anders aus.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Stefanus F. schrieb:
> Ich glaube das ist das einzige Szenario,

und das goto zur fehlerbehandlung ist im professionellen umfeld häufig 
bis standard. also goto ist kein teufelszeug!

es gibt noch viele andere "tricks" z.b. die verwendung von "!!" 
(negation der negation).

schon mal gesehen und den sinn in c verstanden?



mt

von DPA (Gast)


Lesenswert?

Stefanus F. schrieb:
> Hier ein konkretes Code-Beispiel:

Da würde ich die Blöcke für Senden / Empfangen erstmal in 
Unterfunktionen auslagern.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Apollo M. schrieb:
> es gibt noch viele andere "tricks" z.b. die verwendung von "!!"
> (negation der negation).

Sollte den aufmerksamen Lesern von µC.net allseits bekannt sein. Peter 
D. verwendet das sehr gern, um Werte auf 0 und 1 zu "normieren".

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Arduino Fanboy D. schrieb:
> Switch/case ist da nicht unbedingt so effektiv.

wie leicht man doch getäuscht wird ...

switch/case ist nichts anderes als goto in einem anderen gewand


mt

von Stefan F. (Gast)


Lesenswert?

Frank M. schrieb:
>> Hmm, wer hat da wohl von wem abgeguckt. Sieht ja fast so aus, wie mein
>> Beispiel.
>
> Ich weiß nicht, was Du jetzt andeuten willst.

Ich wollte damit andeuten, dass die Ähnlichkeit zwischen W.S Code und 
meinem verblüffend hoch ist. Was für ein bemerkenswerter Zufall!

von wolpino (Gast)


Lesenswert?

Mit dem guten alten do-while(0) Trick kann man es so machen:
1
unsigned int check()
2
{
3
  unsigned int result;
4
5
  do {
6
    result = func1();
7
    if (result != 0) 
8
       break;
9
10
    result = func2();
11
    if (result != 0) 
12
       break;
13
14
    result = func3();
15
    if (result != 0) 
16
       break;
17
18
    // ... viele weitere
19
20
    // alles OK wenn bis hierher geschafft,
21
    // ansonsten vorher 'rausgebreakt'
22
23
  } while(0);
24
25
  return result;
26
}

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

wolpino schrieb:
> Mit dem guten alten do-while(0) Trick kann man es so machen:

Woraus man lernt, dass ein "break" auch nichts anderes als ein goto ist 
;-)

Ja, man sollte goto generell nicht verteufeln. Aber man muss es nicht so 
extensiv wie ein Old-School-Basic-Programmierer einsetzen.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Frank M. schrieb:
> Sollte den aufmerksamen Lesern von µC.net allseits bekannt sein. Peter
> D. verwendet das sehr gern, um Werte auf 0 und 1 zu "normieren".

korrekt! ich bin dann wohl ein spätzünder, weil erst spät realisiert.

ich denke, wir könnten mal breiter über clevere implementation tricks 
reden ... das würde sich lohnen!


mt

von Stefan F. (Gast)


Lesenswert?

wolpino schrieb:
> Mit dem guten alten do-while(0) Trick kann man es so machen:

Äh, das gefällt mir nicht. Wenn ich do/while sehe erwarte ich eine 
Wiederholschleife.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas M. schrieb:
> Frank M. schrieb:
>> unsigned int check()
>> {
>>   unsigned int result;
>>
>>   if ((result = func1()) == 0 &&
>>       (result = func2()) == 0 &&
>>       (result = func3()) == 0)
>>   {
>>     result = do_something ();
>>   }
>>   return result;
>> }
>>
>> Vorteile:
>> - Keine verschachtelten if-Statements
>> - Die Bedingungen stehen übersichtlich untereinander
>> - Die Reihenfolge der func()-Aurufe ist wie gewünscht
>> - Abbruch der func()-Aufrufe, sobald ein Fehler zurückkommt
>> - Keine Klammer-Orgie
>> - Nur ein return
>
> Ich finde es fürchterlich, weil ich eine Zuweisung innerhalb einer
> Abfrage für sowas wie das Ausnützen eines Seiteneffekts halte. Beim

Solche Konstrukte sind eher Sache der Gewohnheit.  Bei den meisten 
Programmen sind Abfragen sehr einfach und der bedingte Code 
komplizierter.  Bei komplexen Aufgaben kann aber auch oft der umgekehrte 
Fall auftreten, sehr komplexe Bedingungen und simple (oder komplexe) 
Aktion.  Oft sind von der Bedingung Werte einzusammeln, ähnlich wie beim 
Pattern-Matching, wo, wenn ein Pattern passt, dieses eingesammelt und 
dann bedingt fortgefahren wird (unter verwendung des gematchten Texts).


> flüchtigen Schnell-drüber-lesen ist die Zuweisung ratzfatz überlesen.

Ist wie gesagt Gewohnheit, auch aufmerksames Lesen bzw. sinnvolle 
Commentare zu schreiben... Und man kann das Konstrukt auch außerhal 
einer Bedinung verwenden:
1
(void)
2
    (result = func1()
3
     || result = func2()
4
     || result = func3()
5
     ...
6
     || result = funcN());
7
8
if (result)
9
   return result;

Hier wird Shortcut-Evaluation benutzt, d.h. die Auswertung des || 
Ausdrucks erfolgt von links nach rechts und wird beendet, sobald result 
!= 0 ist.


> Normalerweise quittieren viele Compiler Zuweisungen innerhalb einer
> Condition direkt mit einer Warnung.

GCC gibt eine Warnung, allerdings nicht wenn die Zuweisung in Klammern 
steht, weshalb Frank die Zuweisungen klammerte.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

wolpino schrieb:
> Mit dem guten alten do-while(0) Trick kann man es so machen:

gefälltt mir! kannte ich nur im kontext mit makros


mt

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Johann L. schrieb:
> Shortcut-Evaluation

... ist sehr nützlich und mächtig, NUR scheuen die hobby/basic jünger 
dieses konstrukt wie das feuer genau so wie z.b.

switch(c=f())
witch(c=f() && ..)
if(c=f() && b=f2() || ...)

hier mal ein beispiel aus der macro ecke zu short-eval alias k-logic

//creates an unique timer for each instance, x: repeats 1-255
#define XPERIODIC(tb, dt, t, x)                 \
  static dt UNIQUE(tmr) = (dt) tb;              \
  static uint8_t UNIQUE(xRepeats) = x;          \
  if ((tb - UNIQUE(tmr) >= t)                   \
  && UNIQUE(xRepeats)                           \
  && (UNIQUE(xRepeats)--, UNIQUE(tmr) = tb, 1)) //UNIQUE(tmr) += t, 1))


mt

von Christian B. (casandro)


Lesenswert?

Ja im Falle dass man Aufräumaktionen machen muss würde ich sogar ein 
goto befürworten.

Sprachfeatures sind in aller Regel nicht an sich gut oder böse, es liegt 
am Programmierer sie sinnvoll und mit Bedacht anzuwenden.

Auch so was wie der "Destruktive Zuweisungsoperator" (= in C) kann für 
absolut furchtbaren Code sorgen. (Deshalb haben den viele Sprachen auch 
gar nicht drin)

von Oliver S. (oliverso)


Lesenswert?

Ich würde es ja sowieso in C++ machen, und da geht es dann elegant ohne 
goto und mehrfachen returns mit throw/catch ;)

Oliver

von Christian B. (casandro)


Lesenswert?

Apollo M. schrieb:
> if(c=f() && b=f2() || ...)

Da garantiert Dir übrigens keiner in welcher Reihenfolge die Funktionen 
ausgeführt werden. Manche Programmierer nehmen da einfach zum Beispiel 
an, dass die von rechts nach links abgearbeitet wird, was aber nicht 
immer der Fall ist.

Das Ergebnis von sowas sind dann "Heisenbugs". Fehler die im Livesystem 
auftreten, im Debuger jedoch nicht, sowie Code der abhängig von der 
Optimierungsstufe unterschiedliche Dinge tut.

: Bearbeitet durch User
von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Christian B. schrieb:
> Da garantiert Dir übrigens keiner in welcher Reihenfolge

darum gilt es dann diese variante zu verwenden

if(c=f(), c && b=f2(), b || ...)


dann sollte es passen!?



mt

von NichtWichtig (Gast)


Lesenswert?

Stefanus F. schrieb:
> wolpino schrieb:
>> Mit dem guten alten do-while(0) Trick kann man es so machen:
>
> Äh, das gefällt mir nicht. Wenn ich do/while sehe erwarte ich eine
> Wiederholschleife.

...deren Abbruchbedingung im while klar und deutlich zu sehen ist.

von Jemand (Gast)


Lesenswert?

Christian B. schrieb:
> Apollo M. schrieb:
>> if(c=f() && b=f2() || ...)
>
> Da garantiert Dir übrigens keiner in welcher Reihenfolge die Funktionen
> ausgeführt werden. Manche Programmierer nehmen da einfach zum Beispiel
> an, dass die von rechts nach links abgearbeitet wird, was aber nicht
> immer der Fall ist.

Nö, && und || garantieren Links-nach-Rechts Auswertung ihrer Operanden 
mit Sequenzpunkten.

von Einer K. (Gast)


Lesenswert?

Apollo M. schrieb:
> Arduino Fanboy D. schrieb:
>> Switch/case ist da nicht unbedingt so effektiv.
>
> wie leicht man doch getäuscht wird ...
>
> switch/case ist nichts anderes als goto in einem anderen gewand
>
> mt

Du hast mich vermutlich nicht richtig verstanden, macht aber auch nix, 
weil switch/case hier sowieso nicht weiter hilft.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Jemand schrieb:
> && und || garantieren Links-nach-Rechts Auswertung ihrer Operanden
> mit Sequenzpunkten.

so ist es! die zweite variante kann vielleicht die lesbarkeit verbessern 
und sonst nichts.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Schade, dass in C die Ergebnisse der && und ||-Operationen auf 0 und 1
beschränkt sind. In Python, wo dies nicht der Fall ist, kann man bspw.
folgendes schreiben:

1
def func1():
2
  print('func1')
3
  return 0  # ok
4
5
def func2():
6
  print('func2')
7
  return 0  # ok
8
9
def func3():
10
  print('func3')
11
  return 13  # fail
12
13
def func4():
14
  print('func4')
15
  return 0  # ok
16
17
18
def check():
19
  result = ( func1() or
20
             func2() or
21
             func3() or
22
             func4()    )
23
24
  if result == 0:
25
    print('do something')
26
27
  return result
28
29
30
result = check()
31
print('result:', result)

Ausgabe:

1
func1
2
func2
3
func3
4
result: 13

https://docs.python.org/3/reference/expressions.html#boolean-operations

von leo (Gast)


Lesenswert?

Johann L. schrieb:
> (void)
>     (result = func1()
>      || result = func2()
>      || result = func3()
>      ...
>      || result = funcN());
>
> if (result)
>    return result;

Das ist falsch, wegen Operator Precedence (result = func1() || result 
...)
Der Compiler (gcc) moniert:
1
if.c:14:16: error: lvalue required as left operand of assignment
2
      || result = funcN());

leo

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

leo schrieb:
> Das ist falsch, wegen Operator Precedence (result = func1() || result
> ...)

klammern, dann geht es!

if ((result = func1()) || (result = func2()) || (result = func3()))
...

mt

von Vincent H. (vinci)


Lesenswert?

Das Beispiel selbst is eigentlich komplett funktional und schreit 
förmlich Monade. Das is in C halt leider eher häßlich und "short 
curcuit"-en tuts auch nicht.
1
#include <assert.h>
2
#include <stddef.h>
3
#include <stdio.h>
4
#include <stdlib.h>
5
6
struct Optional;
7
8
typedef unsigned int (*Fp)();
9
typedef struct Optional* (*AT)(struct Optional*, Fp);
10
11
typedef struct Optional {
12
  AT and_then;
13
  unsigned int result;
14
} optional_t;
15
16
optional_t* and_then(optional_t* o, Fp fp) {
17
  if (o->result == 0 && fp)
18
    o->result = fp();
19
  return o;
20
}
21
22
unsigned int func1() { return 0; }
23
24
unsigned int func2() { return 42; }
25
26
unsigned int func3() {
27
  assert(0);
28
  return 0;
29
}
30
31
unsigned int check() {
32
  optional_t o = {and_then, 0};
33
34
  o.and_then(&o, func1)
35
    ->and_then(&o, func2)
36
    ->and_then(&o, func3);
37
38
  return o.result;
39
}
40
41
int main(void) { check(); }

von Stefan F. (Gast)


Lesenswert?

>> Äh, das gefällt mir nicht. Wenn ich do/while sehe erwarte ich eine
>> Wiederholschleife.

NichtWichtig schrieb:
> ...deren Abbruchbedingung im while klar und deutlich zu sehen ist.

Es ist zweifellos Geschmackssache. Das ist so ein Punkt, den ich hier 
mit euch gerne diskutieren, aber nicht mit meinen Arbeitskollegen - da 
eigentlich total unwichtig.

Ich erwarte bei einer Schleife, dass sie zumindest manchmal öfter als 1x 
durchlaufen wird, was hier nicht der Fall ist. Hier wurde do/while nicht 
verwendet, um eine Wiederholung auszulösen.

von c-hater (Gast)


Lesenswert?

Stefanus F. schrieb:

> Dafür wurde in Java try-finally erfunden. Leider hat C/C++ das nicht.

Mir scheint, dass es das in ObjectPascal/Delphi schon gab, lange bevor 
Java als Sprache überhaupt nur das Licht der Welt erblickt hat...

von A. S. (Gast)


Lesenswert?

Stefanus F. schrieb:
> Hier wurde do/while nicht verwendet, um eine Wiederholung auszulösen.

Stimmt. Aber die alternativen (Endlosschleife oder Switch /dafault) sind 
übler.

do while (0) ist quasi der defakto Standard für einmaligen Durchlauf.

von Walter Kollascheck (Gast)


Lesenswert?

Karl schrieb:
>> Arduino Fanboy D. schrieb:
>> Dumdi D. schrieb:
>> array von funktionspointern
> ...
> In einer Welt in der der geneigte Programmierer zu doof ist, mehrere
> returns zu verkraften bewerfen wir ihn mit Funktionspointern um ein
> dogmatisches Befolgen von allgemeinen "Weisheiten" zu erreichen? Deine
> Kollegen oder Nachfolger werden es danken.

So ein Unfug!
Beim Beispiel des Themenstarters sind Zeiger auf Funktionen 
wahrscheinlich der beste Weg einen übersichtlich Code zu bekommen!

von Peter Petersson (Gast)


Lesenswert?

Mehrfache Returns sind nicht per se schlecht. Nur unnötig viele sind es.

von P. S. (namnyef)


Lesenswert?

Frank M. schrieb:
> unsigned int check()
> {
>   unsigned int result;
>
>   if ((result = func1()) == 0 &&
>       (result = func2()) == 0 &&
>       (result = func3()) == 0)
>   {
>     result = do_something ();
>   }
>   return result;
> }
>
> Vorteile:
> - Keine verschachtelten if-Statements
> - Die Bedingungen stehen übersichtlich untereinander
> - Die Reihenfolge der func()-Aurufe ist wie gewünscht
> - Abbruch der func()-Aufrufe, sobald ein Fehler zurückkommt
> - Keine Klammer-Orgie
> - Nur ein return

Nachteil: Es ist auf den ersten Blick schwer nachzuvollziehen was der 
Code tut. Und das macht leider alle anderen "Vorteile" zunichte.

Es gibt sicher nicht viele legitime Anwendungsfälle von GOTO, aber hier 
wäre es definitv eine Option: Es gibt nur ein return und dennoch ist 
sonnenklar was passiert.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Walter Kollascheck schrieb:
> sind Zeiger auf Funktionen
> wahrscheinlich der beste Weg einen übersichtlich Code zu bekommen!

ich melde dann schon mal wiederspruch an!
ich sehe hier keinen echten sinn/zwang/vorteil von function pointer 
verwendung.


mt

von P. S. (namnyef)


Lesenswert?

Also bei manchen Vorschlägen hier bekommt man echt Augenkrebs. Es geht 
darum möglichst wartbaren Code zu schreiben. Da bleibt man dann lieber 
gleich bei mehreren returns bevor man solche wilden Konstrukte bastelt.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Peter Petersson schrieb:
> Mehrfache Returns sind nicht per se schlecht.

das sehe ich auch so und scheue auch nicht davor zurück wenige/viele - 
wenn es sinnig erschein, einzusetzen.


mt

von A. S. (Gast)


Lesenswert?

Das eigentliche Problem liegt darin, dass der Rückgabewert Ergebnis und 
Erfolgsflag ist.

Wäre das Ergebnis egal, würde man einfach schreiben:
1
if(func1()) {return 1;}

Im TO-Beispiel würde ich "Blocksatz" empfehlen:
1
int r;
2
   
3
   r=func1();. if(r) {return r;}
4
   r=func2();. if(r) {return r;}
5
   ...
6
   r=func99(); if(r) {return r;}

von zitter_ned_aso (Gast)


Lesenswert?

Apollo M. schrieb:
> ich melde dann schon mal wiederspruch an!
> ich sehe hier keinen echten sinn/zwang/vorteil von function pointer
> verwendung.

ich denke dass so ein Array hier sehr wohl Sinn macht.

Mike schrieb:
> result = func3();
>   if (result != 0)
>      return result;
>
>   ... // viele weitere



Und viele weitere.

Wer will das schon per hand machen.

von zitter_ned_aso (Gast)


Lesenswert?

A. S. schrieb:
> Im TO-Beispiel würde ich "Blocksatz" empfehlen:

warum? welche Vorteile bringt das?

besser eine Anweisung pro Zeile als drei (finde ich).

von A. S. (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> warum? welche Vorteile bringt das?

einen hast Du selbst genannt.
zitter_ned_aso schrieb:
> Und viele weitere.
>
> Wer will das schon per hand machen.

Sobald Du 20-30 davon hast(*) ist Blocksatz besser schreib- und lesbar. 
Was gleich ist, ist gleich (copy/paste), nur eine Spalte ist fortlaufend 
bzw. individuell zu ändern bzw. geändert.

Du siehst mit einem Blick: Das Muster ist immer gleich, verstehe ich 
eine Zeile, verstehe ich alle, keine Sonderbehandlung im 17.ten Fall.

Erst recht, wenn Du mehrere "Spalten" ändern musst, z.B. weil func1 mit 
A1, B1, C1 aufgerufen wird.

Ich gehe manchmal sogar hin, und packe das ganze in ein Makro qd (heisst 
immer qd) in der Zeile davor, mit undef dahinter, z.B.
1
/* DUMMY_STATEMENT reserviert für das Semikolon am Ende */
2
#define qd(t) ret=FuncType_##t(PAR1_TYP_##t, PAR2_TYP_##t, PAR2_TYP_##t);\
3
              if(ret) {return ret;}\
4
              DUMMY_STATEMENT
5
  qd(1);
6
  qd(2); 
7
  ...
8
  qd(99);
9
#undef qd

Der für mich große Nachteil dabei ist jetzt, dass ich z.B. "PAR1_TYP_1" 
im Suchlauf per Editor nicht finde. Darum rolle ich die Schleifen (im 
Blocksatz) meist doch aus.


(*) und aus welchenn Gründen auch immer dann trotzdem keine Func-Ptr 
willst. Und ja, wenn es geht, nimmt man Arrays auch für Parameter, aber 
manchmal geht es halt nicht.

von Dumdi D. (dumdidum)


Lesenswert?

Apollo M. schrieb:
> ich sehe hier keinen echten sinn/zwang/vorteil von function pointer
> verwendung.

Die Frage ist im Prinzip gleich zu:
Ich habe mehrere ints a1, a2, a3,... und muss damit irgendetwas machen. 
Da sagt doch auch jeder nimm ein Array. Der einzige Unterschied ist halt 
das nicht jeder 'sattelfest' mit Funktionenpointern ist.

von Peter D. (peda)


Lesenswert?

Apollo M. schrieb:
> ich sehe hier keinen echten sinn/zwang/vorteil von function pointer
> verwendung.

Der Vorteil von Schleifen ist, man muß sich nur einen einzigen Durchlauf 
anschauen, verstehen und testen.
Ein weiterer Vorteil ist, man kann sehr bequem Elemente hinzufügen oder 
löschen. Man muß dazu nicht mal den Code anfassen, sondern nur das 
Array.
Für das Schleifenende nehme ich gerne einen Null-Pointer als letztes 
Element. Dann kann die Funktion auch über verschiedene Arrays laufen.
Ich nehme oft schon ab 2 gleichen Aktionen eine Schleife. Damit 
vermeidet man optimal copy&paste Fehler.

von mh (Gast)


Lesenswert?

Peter D. schrieb:
> Apollo M. schrieb:
>> ich sehe hier keinen echten sinn/zwang/vorteil von function pointer
>> verwendung.
>
> Der Vorteil von Schleifen ist, man muß sich nur einen einzigen Durchlauf
> anschauen, verstehen und testen.
> Ein weiterer Vorteil ist, man kann sehr bequem Elemente hinzufügen oder
> löschen. Man muß dazu nicht mal den Code anfassen, sondern nur das
> Array.
> Für das Schleifenende nehme ich gerne einen Null-Pointer als letztes
> Element. Dann kann die Funktion auch über verschiedene Arrays laufen.
> Ich nehme oft schon ab 2 gleichen Aktionen eine Schleife. Damit
> vermeidet man optimal copy&paste Fehler.

Man muss sich aber bewusst sein, dass man auf ein Elementtyp beschränkt 
ist. Wenn eine der Funktionen plötzlich Argumente braucht, muss man 
alles wieder ändern, oder Ausnahmen einführen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

mh schrieb:
> Wenn eine der Funktionen plötzlich Argumente braucht, muss man
> alles wieder ändern, oder Ausnahmen einführen.

Das sehe ich ebenfalls so.

Aber selbst wenn garantiert werden kann, dass die Funktionen auch
zukünftig immer vom selben Typ sind, gefällt mir hier die Methode ohne
Schleife besser:

1
int check1(void) {                   int check2(void) {
2
  int res;                             typedef int (*func_t)(void);
3
                                       static func_t funcs[] = {
4
  if( (res = func1()) ||                 func1, func2, func3, func4, 0
5
      (res = func2()) ||               };
6
      (res = func3()) ||               int res;
7
      (res = func4())  )
8
    return res;                        for(func_t *f = funcs; *f; f++) {
9
                                         if((res = (*f)()))
10
  doSomething();                           return res;
11
  return 0;                            }
12
}
13
                                       doSomething();
14
                                       return 0;
15
                                     }

von Einer K. (Gast)


Lesenswert?

Yalu X. schrieb:
> gefällt mir hier die Methode ohne
> Schleife besser:

Zumindest ist sie schön "rechteckig".
Und damit übersichtlich und leicht um weitere Tests zu erweitern.

von Oliver S. (oliverso)


Lesenswert?

Yalu X. schrieb:
> Aber selbst wenn garantiert werden kann, dass die Funktionen auch
> zukünftig immer vom selben Typ sind, gefällt mir hier die Methode ohne
> Schleife besser:

Bei den Trivial-Beispiel mit nur 4 Funktionen ist das so.

Wenn es sehr viel mehr wären (was in dem Kontext allerdings eher selten 
der Fall sein wird), hat die Funktionspointer-Schleife schon Vorteile.

Oliver

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Oliver S. schrieb:
> Yalu X. schrieb:
>> Aber selbst wenn garantiert werden kann, dass die Funktionen auch
>> zukünftig immer vom selben Typ sind, gefällt mir hier die Methode ohne
>> Schleife besser:
>
> Bei den Trivial-Beispiel mit nur 4 Funktionen ist das so.
>
> Wenn es sehr viel mehr wären (was in dem Kontext allerdings eher selten
> der Fall sein wird), hat die Funktionspointer-Schleife schon Vorteile.

Das ist richtig. Bei mehr als etwa 10 Funktionen würde ich sie auch in
eine Schleife packen. Allerdings hatte ich diesen Fall noch nie. Bei so
vielen Funktionen ist zudem die Chance groß, dass sie untereinander sehr
ähnlich sind, so dass man sie evtl. in eine einzelne Funktion (mit einer
internen Schleife) zusammenfassen kann.

von Wilhelm M. (wimalopaan)


Lesenswert?

Würde man C endlich mal an die Seite legen und C++ benutzen und auch 
einige grundlegende Regeln der Modellierung einhalten (z.B. ein `int` 
ist ein Datentyp, der keine ungültigen Werte wie etwa 0 oder -1 besitzt) 
würde, dann wäre es allgemein und simpel lösbar:
1
#include <optional>
2
#include <iostream>
3
4
using value_type = std::optional<int>;
5
6
value_type func1() {
7
    return 1;
8
}
9
value_type func2() {
10
    return 2;
11
}
12
13
value_type func3() {
14
    return {};
15
}
16
17
template<typename... F>
18
value_type check(F... ff) {
19
    value_type result;
20
    ((result = ff()) || ...);
21
    return result;
22
}
23
24
int main() {
25
    auto r = check(func3, func1, func2);
26
    if (r) {
27
        std::cout << *r << '\n';
28
    }
29
}

Das Überprüfen zur Compilezeit, ob alle Funktionen tatsächlich vom 
selben Typ sind, ist zwar simpel und gehört dahinein, überlasse ich aber 
dem geneigten Leser.
Und ja, ich habe gelesen, dass nach C und nicht nach C++ gefragt war. 
Nur bei den obigen Vorschlägen ... sorry!

von batman (Gast)


Lesenswert?

Peter D. schrieb:
> Apollo M. schrieb:
>> ich sehe hier keinen echten sinn/zwang/vorteil von function pointer
>> verwendung.
>
> Der Vorteil von Schleifen ist, man muß sich nur einen einzigen Durchlauf
> anschauen, verstehen und testen.
> Ein weiterer Vorteil ist, man kann sehr bequem Elemente hinzufügen oder
> löschen. Man muß dazu nicht mal den Code anfassen, sondern nur das
> Array.

Bei so einem simplen Progammtext wie eingangs existiert doch nicht 
ernsthaft ein Problem mit anschauen oder verstehen.
Was man da ausgerechnet durch extra Arrays, Beschränkungen von 
Funktionsnamen auf Nummern oder umständlich noch mit extra Zuweisungen 
reißen will, ist mir ein Rätsel.

von PittyJ (Gast)


Lesenswert?

Ich finde die bisherige Lösung mit mehreren returns immer noch die 
beste.
Die ist übersichtlich. Einfach zu erstellen. Jeder andere Programmierer 
weiss auch, was dort passiert. Sie hat wenig Overhead.

Warum also sich etwas anderes aus den Fingern saugen, wo die Gefahr 
besteht, dass Übersicht verloren geht, oder noch mehr Fehler eingebaut 
werden können.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Vincent H. schrieb:
> Das Beispiel selbst is eigentlich komplett funktional und schreit
> förmlich Monade. Das is in C halt leider eher häßlich und "short
> curcuit"-en tuts auch nicht.

Ja, Monaden machen erst dann Spaß, wenn die verwendete Sprache auch den
entsprechenden syntaktischen Zucker dazu liefert.

Dein C-Beispiel

Vincent H. schrieb:
> o.and_then(&o, func1)
>     ->and_then(&o, func2)
>     ->and_then(&o, func3);

würde bspw. in Haskell zu einem einfachen

1
  do
2
    func1
3
    func2
4
    func3

Der Rückgabewert der Funktionen wäre bspw. Either Int (), d.h. im
Fehlerfall geben sie einen Fehlercode als Int zurück, sonst ein Nichts.

Selbst ungezuckert (d.h. ohne do-Notation) ist der Ausdruck in diesem
konkreten Beispiel nicht viel komplizierter:

1
func1 >> func2 >> func3

Liegen die Funktionen bereits in einer Liste vor

1
funcs = [ func1, func, func3 ]

erreicht man dasselbe mit

1
  sequence_ funcs

Nur schade, dass es keinen Haskell-Compiler für kleine Mikrocontroller
gibt ;-)

von Wilhelm M. (wimalopaan)


Lesenswert?

batman schrieb:
> Peter D. schrieb:
>> Apollo M. schrieb:
>>> ich sehe hier keinen echten sinn/zwang/vorteil von function pointer
>>> verwendung.
>>
>> Der Vorteil von Schleifen ist, man muß sich nur einen einzigen Durchlauf
>> anschauen, verstehen und testen.
>> Ein weiterer Vorteil ist, man kann sehr bequem Elemente hinzufügen oder
>> löschen. Man muß dazu nicht mal den Code anfassen, sondern nur das
>> Array.
>
> Bei so einem simplen Progammtext wie eingangs existiert doch nicht
> ernsthaft ein Problem mit anschauen oder verstehen.

Der Code hat mehrere Probleme: Datentypen werden falsch verwendet und er 
ist nicht generisch. Das mehrfache `return` ist in der Tat gar kein 
Problem.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> und er
> ist nicht generisch

Mike schrieb:
> c-Funktion

Mach mal einen Vorschlag...

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Wilhelm M. schrieb:
>> und er
>> ist nicht generisch
>
> Mike schrieb:
>> c-Funktion
>
> Mach mal einen Vorschlag...
>
> Oliver

Steht doch oben ...

von Bernd K. (prof7bit)


Lesenswert?

Yalu X. schrieb:
> Either

Nicht eher ein Maybe?

von Peter D. (peda)


Lesenswert?

Wilhelm M. schrieb:
> Der Code hat mehrere Probleme:

Welcher denn?
Der zitierte Post enthält gar keinen Code.
Beitrag "Re: c: Wie multiple return-Statements vermeiden?"

von Wilhelm M. (wimalopaan)


Lesenswert?

Peter D. schrieb:
> Wilhelm M. schrieb:
>> Der Code hat mehrere Probleme:
>
> Welcher denn?
> Der zitierte Post enthält gar keinen Code.
> Beitrag "Re: c: Wie multiple return-Statements vermeiden?"

Siehe Beitrag #1 in diesem Thread ;-) Darum ging es doch, oder?

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

c++?

für mich ist "Rust" der zukünftige stern am embbeded himmel, ein 
würdiger ablöser der c ersthaft ersetzen kann auch im 8bit segment!

mt

von Wilhelm M. (wimalopaan)


Lesenswert?

Apollo M. schrieb:
> c++?

absolut ja (s.o.).

> für mich ist "Rust" der zukünftige stern am embbeded himmel, ein
> würdiger ablöser der c ersthaft ersetzen kann auch im 8bit segment!

Das denke ich auch, es geht schon vieles!

Den Hinweis auf C++ habe ich oben gebracht, weil man damit auch als 
C-Programmierer viele Vorteile genießen kann, die wirklich wichtig sind: 
domänen-spezifische Datentypen und Generizität.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Bernd K. schrieb:
> Yalu X. schrieb:
>> Either
>
> Nicht eher ein Maybe?

Es soll im Abbruchfall ein Errorcode zurückgegeben werden (mit Either
also bspw. ein Left 13). Geht es nur um den Abbruch als solchen, reicht
auch ein Maybe mit Rückgabewert Nothing.

In beiden Fällen könnte auch im Erfolgsfall ein Ergebnis zurückgegeben
werden, so dass bspw. auch folgendes möglich ist:

1
  do
2
    x <- func1
3
    y <- func2
4
    z <- func3
5
    doSomethingWith x y z

Aber das alles ist ziemlich offtopic, da es in dem Thread ja um C und
nicht um Haskell geht. Ich bin nur deswegen daraufgekommen, weil Vincent
weiter oben das Thema Monade angerissen hat, was zwar ein guter Ansatz
ist, aber nach seiner eigenen Aussage in C eher häßlich aussieht. In
Haskell, wo Monaden ein zentrales Sprachelement darstellen, verschwindet
diese Häßlichkeit komplett. Auch in C# lassen sich Monaden dank LINQ auf
recht ansprechende Weise nutzen. Leider muss man sich in C# m.W. die
benötigten Datentypen wie Maybe oder Either erst selber zusammenbasteln.

von Christian B. (casandro)


Lesenswert?

Apollo M. schrieb:
> Christian B. schrieb:
>> Da garantiert Dir übrigens keiner in welcher Reihenfolge
>
> darum gilt es dann diese variante zu verwenden
>
> if(c=f(), c && b=f2(), b || ...)
>
> dann sollte es passen!?

Alleine die Tatsache dass man signifikant darüber nachdenken muss zeigt, 
dass es nicht wirklich gut ist. Code muss möglichst sofort verständlich 
sein. Jedes Stück Hirnschmalz, welches Du in das Parsen des Codes 
investiert, fehlt Dir später beim Verstehen was das Programm überhaupt 
macht.

von W.S. (Gast)


Lesenswert?

Mike schrieb:
> Nun gelten mehrfache returns bekanntlich als schlechter Programmierstil.

Nö.

Nur bei solchen selbsternannten Gurus, denen nichts Besseres eingefallen 
ist, um ne zusätzliche Regel zu erfinden.

Das Einzige, was wirklich gilt, ist ob deine Quelle der Syntax von C 
entspricht und der Compiler folglich selbige übersetzen kann oder nicht.



Rufus Τ. F. schrieb:
> Bestimmte Konstrukte sind mit mehrfachen Returns klar, kurz und
> übersichtlich.
>
> Manche Konstrukte werden durch krampfhaftes Vermeiden mehrfacher Returns
> sogar ausgesprochen unübersichtlich.

Dem stimme ich ausdrücklichst zu.

Und man kann das auch erweitern auf einige andere Konstrukte.

W.S.

von Bernd K. (prof7bit)


Lesenswert?

Yalu X. schrieb:
> Auch in C# lassen sich Monaden dank LINQ auf
> recht ansprechende Weise nutzen. Leider muss man sich in C# m.W. die
> benötigten Datentypen wie Maybe oder Either erst selber zusammenbasteln.

Rust hat das übrigens auch, da heißen sie Option und Result (und man 
kann sich selber welche machen, das ist eine clevere Erweiterung des 
enum-Datentyps). Aber wie man das obige dann am elegantesten in Rust 
schreiben würde kann ich auch nicht sagen, ich bin da noch nicht richtig 
eingestiegen. Aber immerhin gibts das auch für ARM Controller.

von Wilhelm M. (wimalopaan)


Lesenswert?

Christian B. schrieb:
> Apollo M. schrieb:
>> Christian B. schrieb:
>>> Da garantiert Dir übrigens keiner in welcher Reihenfolge
>>
>> darum gilt es dann diese variante zu verwenden
>>
>> if(c=f(), c && b=f2(), b || ...)
>>
>> dann sollte es passen!?
>
> Alleine die Tatsache dass man signifikant darüber nachdenken muss zeigt,
> dass es nicht wirklich gut ist. Code muss möglichst sofort verständlich
> sein.

genau! Deswegen sollte man eben möglichst domänen-spezifische Datentypen 
kreieren und den Code möglichst generisch halten.
1
#include <optional>
2
#include <type_traits>
3
#include "../lib/meta.h"
4
#include "../lib/concepts.h"
5
6
using namespace BMCPP;
7
using namespace BMCPP::Meta;
8
9
using value_type = unsigned int;
10
using result_type = std::optional<value_type>;
11
12
volatile value_type x1 = 1;
13
volatile value_type x2 = 2;
14
volatile value_type x3 = 0;
15
16
result_type func1() {
17
    return x1;
18
}
19
result_type func2() {
20
    return x2;
21
}
22
result_type func3() {
23
    return {};
24
}
25
26
template<typename>
27
struct isOptional : std::false_type {};
28
template<typename T>
29
struct isOptional<std::optional<T>> : std::true_type {};
30
31
template<Callable... FF>
32
auto first_valid_result_of(FF... ff) {
33
    using first_type = front<List<decltype(ff())...>>;
34
    static_assert(isOptional<first_type>::value, "functions must return optional<T>");
35
    static_assert((std::is_same<first_type, decltype(ff())>::value && ...), "all functions must return same type");
36
    result_type result;
37
    ((result = ff()) || ...);
38
    return result;
39
}
40
41
int main() {
42
    auto r = first_valid_result_of(func1, func1, func3);
43
    if (r) {
44
        return *r;
45
    }
46
    return 42;
47
}

Die Funktion `first_valid_result_of()` ist generisch mit einigen 
Typanforderungen, etwa, dass alle Callables, die als Argumente übergeben 
werden, vom selben Typ std::optional<> sind.

Wer die Meta-Funktion isOptional<> und front<> und die Type-Liste und 
die static_asserts nicht mag, kann sie gerne entfernen, dann sind wir 
wieder beim ursprünglichen Dreizeiler. Allerdings sind sie wichtig, 
damit falscher Code ersr gar nicht compiliert.

von zitter_ned_aso (Gast)


Lesenswert?

o_O

wie viele Programmiersprachen könnt ihr??? und wie behält man das alles 
im Kopf / dabei nicht durcheinander kommt?

von batman (Gast)


Lesenswert?

Karl May beherrschte 1200 Sprachen und Dialekte.

von A. S. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> genau! Deswegen sollte man eben möglichst domänen-spezifische Datentypen
> kreieren

Jetzt hört doch auf damit. Christian hat sich vertan (Anfänger oder noch 
nie gebraucht), Apollo ebenso, und nun werden wilde workarounds für ein 
ideomatisches, zuverlässiges Konstrukt diskutiert.

Wenn ein Verhalten explizit definiert ist (hier: Auswertung von links 
nach rechts), dann ist es sinnlos, das zu leugnen.

von Echtter Programmierer (Gast)


Lesenswert?

Yalu X. schrieb:
> Es soll im Abbruchfall ein Errorcode zurückgegeben werden (mit Either
> also bspw. ein Left 13). Geht es nur um den Abbruch als solchen, reicht
> auch ein Maybe mit Rückgabewert Nothing.

Nö.

Monaden? Either? Ziemlich am Thema vorbei:

Der Fragesteller möchte nacheinander ein paar Funktionen ausprobieren, 
und das Ergebnis der ersten Funktion nutzen die kein Fehler 
signalisiert. Signalisieren alle Funktionen einen Fehler, soll wiederum 
ein Fehler signalisiert werden.

Weil er in C programmiert, macht er es mit dem Returncode. In Haskell 
würde man dafür natürlich ein Maybe verwenden.

Haskell hat Lazy-Evaluation, das macht die Sache Pippi-einfach:

Angenommen, er hat ein Funktionsaufrufe (bzw. Konstanten) die 0 im 
Fehlerfall zurückgeben, ist die Lösung:
1
let check = fromMaybe 0 . find (/=0) $ [func1 ..., func2 ..., func3 ...]

Das wars. Nix Monade. Einfacher geht's wohl kaum.


Angenommen, die Funktionen geben ein Maybe zurück, kommt ein Monoid zum 
Einsatz:
1
let check = getFirst . foldMap First $ [func1 ..., func2 ..., func3 ...]

Wieder keine Monade... Und gibt selbst ein Maybe zurück; macht ja 
Sinn...


In der Realität will man die Funktionalität von "check" natürlich nicht 
als Konstante, sondern als Funktion. Dann auch mit anderem Namen:
1
let acceptFirstResult x = getFirst . foldMap First . map ($ x)
2
3
let x = ...
4
let check = acceptFirstResult x [func1, func2, func3]


Angenommen eine Funktion gibt ein "Either" zurück, naja, macht ja nix:
1
let x = ...
2
let check = acceptFirstResult x [func1, rightToMaybe . func2, func3]


Hat man tatsächlich Funktionen, die einen Fehler mit einem Wert 
signalisieren, baut man sich eben wieder einen kleinen Helper:
1
let toNothing p x = if x == p then Nothing else Just x
2
3
let x = ...
4
let check = acceptFirstResult x [func1, toNothing 0 . func2, func3]

usw., etc.


Merke: Mit Haskell sollte man nur dann um sich werfen, wenn man etwas 
mehr als ein Tutorial hinter sich hat...

von Wilhelm M. (wimalopaan)


Lesenswert?

A. S. schrieb:
> Wilhelm M. schrieb:
>> genau! Deswegen sollte man eben möglichst domänen-spezifische Datentypen
>> kreieren
>
> Jetzt hört doch auf damit. Christian hat sich vertan (Anfänger oder noch
> nie gebraucht), Apollo ebenso, und nun werden wilde workarounds für ein
> ideomatisches, zuverlässiges Konstrukt diskutiert.
>
> Wenn ein Verhalten explizit definiert ist (hier: Auswertung von links
> nach rechts), dann ist es sinnlos, das zu leugnen.

Was hat das mit Datentypen zu tun? Was habe ich geleugnet?

von Stefan F. (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> wie viele Programmiersprachen könnt ihr??? und wie behält man das alles
> im Kopf / dabei nicht durcheinander kommt?

DOS/Windows Batch, GwBasic, C64 Basic, Visual Basic, Visual Basic for 
Applications, Bash, SED, AWK, Pascal, C, C++, Assembler, PL/SQL, Python, 
Java, Javascript, Perl, TCL/TK, PHP, ... (Ich habe sicher noch welche 
vergessen)

Manchmal werfe ich etwas durcheinander. Die spezifischen 
Entwicklungsumgebungen helfen dabei, wenigstens die Syntax einzuhalten. 
Und Google - ohne Internet und Google wäre ich aufgeschmissen. Die 
Zeiten von Turbo Pascal unter DOS, wo man das ganze Framework fast 
auswendig lernen konnte, sind definitiv vorbei.

Man programmiert meistens mit dem, was einem vorgeschrieben wird. 
Flexibilität hat noch keinem geschadet, so ist man weniger arbeitslos.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Echtter Programmierer schrieb:
> Yalu X. schrieb:
>> Es soll im Abbruchfall ein Errorcode zurückgegeben werden (mit Either
>> also bspw. ein Left 13). Geht es nur um den Abbruch als solchen, reicht
>> auch ein Maybe mit Rückgabewert Nothing.

> Der Fragesteller möchte nacheinander ein paar Funktionen ausprobieren,
> und das Ergebnis der ersten Funktion nutzen die kein Fehler
> signalisiert. Signalisieren alle Funktionen einen Fehler, soll wiederum
> ein Fehler signalisiert werden.

Ups, da habe ich den Eröffnungsbeitrag nicht aufmerksam genug gelesen.
Ich bin irrtümlicherweise davon ausgegangen, dass 0 Erfolg signalisiert
und von 0 verschiedene Werte einen Fehlercode darstellen (ich hatte noch
einen anderen Thread im Hinterkopf, wo es tatsächlich so war).

Das ändert aber nichts daran, dass meine Code-Beispiele das gewünschte
Verhalten (vorzeitiger Abbruch bei validem Funktionswert) und Ergebnis
(erster valider Funktionswert) liefern. Lediglich meine Interpretation
der Ergebnisse in den begleitenden Texten sind verkehrt herum. Ersetze
in diesen Texten "Erfolg" (und ähnliche Begriffe) durch "Fehler" und
umgekehrt, dann sollte das Meiste stimmen.

> Monaden? Either? Ziemlich am Thema vorbei:

Streng genommen sind alle Beiträge zum Thema Haskell, C++, Python, Rust
usw. am Thema vorbei, da der TE nach C fragte. Nachdem aber so ziemlich
alle denkbaren Alternativlösungen in C längst abgehakt sind, ist es auch
nicht schlimm, wenn der Themenhorizont etwas erweitert wird.

Was das Thema Monaden betrifft, bezog ich mich dabei darauf:

Vincent H. schrieb:
> Das Beispiel selbst is eigentlich komplett funktional und schreit
> förmlich Monade.

Ich habe übrigens nirgends behauptet, dass es nicht auch ohne Monade
geht.

> Weil er in C programmiert, macht er es mit dem Returncode. In Haskell
> würde man dafür natürlich ein Maybe verwenden.

Ach, und was ist Maybe?

1
>> :i Maybe
2
...
3
instance Monad Maybe
4
...

> Haskell hat Lazy-Evaluation, das macht die Sache Pippi-einfach:
>
> Angenommen, er hat ein Funktionsaufrufe (bzw. Konstanten) die 0 im
> Fehlerfall zurückgeben, ist die Lösung:
> let check = fromMaybe 0 . find (/=0) $ [func1 ..., func2 ..., func3 ...]

Was ist daran jetzt so viel einfacher als bei

1
check = do
2
  func1
3
  func2
4
  func3

Ja, mir ist klar, dass die beiden Beispiele nicht ganz identisch sind,
da bei dir die Rückgabewerte von der Typklasse Num und Eq, bei mir
hingegen vom Typ Either a () sind. Dabei kann a ein beliebiger Typ sein,
und für den Erfolgsfall steht der komplette Wertebereich dieses Typs zur
Verfügung. Bei dir (wie auch beim TE) hat die 0 einen Sonderstatus, den
man eigentlich vermeiden möchte, da er die Generizität einschränkt.

Warum sollte man also der Lösung, die kürzer, übersichtlicher und dazu
noch generischer ist, nicht den Vorzug geben?

Außerdem sind Sprachfeatures (in diesem Fall Monaden) dazu da, um
genutzt zu werden, und nicht um zu zeigen "ich kann's auch ohne".

> Merke: Mit Haskell sollte man nur dann um sich werfen, wenn man etwas
> mehr als ein Tutorial hinter sich hat...

Ja, ja, arbeite erst einmal die zweite Hälfte des Tutorials durch, dann
wirst du viele Dinge besser verstehen :)

: Bearbeitet durch Moderator
von Echter Programmierer (Gast)


Lesenswert?

Yalu X. schrieb:

> Ach, und was ist Maybe?
>
>>> :i Maybe
> ...
> instance Monad Maybe
> ...

Und? Wo in meinen Beispielen wird der monadische Berechnung verwendet? 
Nirgends!


> Was ist daran jetzt so viel einfacher als bei
>
> check = do
>   func1
>   func2
>   func3

Naja, es ist halt falsch...

Sowohl Maybe-Monade wie auch Either-Monade machen etwas ganz anderes. 
Sie brechen ab, sobald eine Berechnung "Nothing" bzw. "Left" ergibt.

Dein obiges Beispiel ergibt in der Maybe-Monade Nothing falls func1 oder 
func2 Nothing sind, ansonsten das Resultat von func3. In der 
Either-Monade genau das gleiche, nur eben mit Left für func1 und func2.

Probier's doch einfach aus, wenn Du ghci eh schon auf hast:


[code]
> Just 1 >> Just 2 >> Just 3
Just 3

> Just 1 >> Nothing >> Just 3
Nothing
[/code}


Die Aufgabe ist aber eine andere. Nämlich das erste Just aus einer Reihe 
von Maybes oder eben das erste Right aus einer Reihe von Eithers.


> Ja, ja, arbeite erst einmal die zweite Hälfte des Tutorials durch, dann
> wirst du viele Dinge besser verstehen :)

Bis jetzt bist Du mit bezüglich Haskell ziemlich unterlegen, wie Du hier 
selbst demonstrierst.

von Echter Programmierer (Gast)


Lesenswert?

Echter Programmierer schrieb:
> Die Aufgabe ist aber eine andere. Nämlich das erste Just aus einer Reihe
> von Maybes oder eben das erste Right aus einer Reihe von Eithers.

Tip halt mal folgendes in ghci und lerne:
1
> getFirst . foldMap First $ [Just 1, Nothing, Just 3]
2
Just 1
3
4
> getFirst . foldMap First $ [Nothing, Nothing, Just 3]
5
Just 3
6
7
> getFirst . foldMap First $ [Nothing, Nothing, Nothing]
8
Nothing

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Echter Programmierer schrieb:
> Tip halt mal folgendes in ghci und lerne:

Das geht jetzt hier total am Thema vorbei. Eröffne bitte dazu einen 
neuen Thread, um Vor-/Nachteile von Haskell & Co anhand von Beispielen 
anzuführen ... und Dein Können zu demonstrieren.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Echter Programmierer schrieb:
> Sowohl Maybe-Monade wie auch Either-Monade machen etwas ganz anderes.
> Sie brechen ab, sobald eine Berechnung "Nothing" bzw. "Left" ergibt.

Von Maybe habe ich nichts geschrieben, sondern von Either, das wie folgt
parametriert ist:

Yalu X. schrieb:
> Der Rückgabewert der Funktionen wäre bspw. Either Int ()

Oder allgemeiner:

Yalu X. schrieb:
> vom Typ Either a ()

D.h. das Ergebnis wird in Left zurückgegeben, was, wie du richtig
schreibst, zum Abbruch führt.

Wenn du das Thema weiter ausbreiten möchtest, starte – wie von Frank
vorgeschlagen – einen neuen Thread, damit dieser hier nicht zu sehr
überladen wird. Ich bin dann gerne mit dabei.

von Echter Programmierer (Gast)


Lesenswert?

Yalu X. schrieb:
> das Ergebnis wird in Left zurückgegeben, was, wie du richtig
> schreibst, zum Abbruch führt.

Was ziemlich dämlich ist, da per Konvention Right das Ergebnis ist und 
Left ein Fehler.


Aber gegen zwei Moderatoren kann ich nichts ausrichten und beende 
hiermit...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Echter Programmierer schrieb:
> Bis jetzt bist Du mit bezüglich Haskell ziemlich unterlegen

Echter Programmierer schrieb:
> Aber gegen zwei Moderatoren

Das ist hier kein Wettbewerb oder so etwas. Mach einfach einen neuen 
Haskell-Thread auf und gut ist.

von Niklas G. (erlkoenig) Benutzerseite


Angehängte Dateien:

Lesenswert?

Wilhelm M. schrieb:
> genau! Deswegen sollte man eben möglichst domänen-spezifische Datentypen
> kreieren und den Code möglichst generisch halten.

Darauf hab ich gewartet :) Aus Spaß im Anhang eine aufgetunte Version 
davon.
Nutzungsbeispiel:
1
using result_type = std::optional<unsigned int>;
2
3
result_type func1(int a, float b, std::string c) {
4
  std::cout << "func1(" << a << ", " << b << ", " << c << ")\n";
5
  return { };
6
}
7
result_type func2(int a, float b, std::string c) {
8
  std::cout << "func2(" << a << ", " << b << ", " << c << ")\n";
9
  return { 0 };
10
}
11
result_type func3(int a, float b, std::string c) {
12
  std::cout << "func3(" << a << ", " << b << ", " << c << ")\n";
13
  return { 2 };
14
}
15
16
int main() {
17
  auto r = first_valid_result_of(func1, func2, func3)(42, 3.14, "foo");
18
  if (r) {
19
    std::cout << *r << std::endl;
20
  } else
21
    std::cout << "Nothing\n";
22
    return 0;
23
}

Features:
- Kann beliebige Parameter an die Funktionen übergeben (zweites 
Klammerpaar)
- Ist constexpr
- Funktioniert mit beliebigem Rückgabetyp, sofern dieser nach "bool" 
konvertierbar und move-constructible ist
- Der Rückgabetyp wird automatisch ermittelt und zurückgegeben
- Es wird der erste Rückgabewert zurückgegeben, der nach bool 
konvertiert "true" ist
- Liefert kein Wert "true", wird ein per Standardkonstruktor erstellter 
Rückgabewert zurückgegeben
- Es wird nicht 1 Instanz des Rückgabetyps erstellt und wiederholt 
zugewiesen, sondern immer nur 1 Instanz erstellt, geprüft, und 
zurückgegeben oder zerstört. Funktioniert somit auch mit 
nicht-zuweisbaren oder nicht-kopierbaren Typen.
- Funktioniert somit z.B. mit Integer-Rückgabetypen (mit 0 = "Fehler") 
und mit std::optional oder std::unique_ptr
- Hat keine Abhängigkeiten außer C++17-Standardbibliothek

von Wilhelm M. (wimalopaan)


Lesenswert?

Niklas G. schrieb:
> Wilhelm M. schrieb:
>> genau! Deswegen sollte man eben möglichst domänen-spezifische Datentypen
>> kreieren und den Code möglichst generisch halten.
>
> Darauf hab ich gewartet :) Aus Spaß im Anhang eine aufgetunte Version
> davon.

Dankeschön! Endlich hat es jemand verstanden ;-)

von Heiko L. (zer0)


Lesenswert?

In meinen Augen sind das tolle Toy-Examples. In c++ hat man für soetwas 
Exceptions parat. Da gibt es keinen Grund, das gesamte Programm in ein 
einzelnes Statement zu stopfen.
Wo sollten die Kommentare stehen? Triviale Frage - "Kommentare schreibe 
ich sowieso nie".

von Stefan F. (Gast)


Lesenswert?

Heiko L. schrieb:
> In c++ hat man für soetwas Exceptions parat.

In Java sind Exceptions "teuer" ,deswegen meidet man dort deren 
Benutzung für normale Fälle, die häufig auftreten. Sind Exceptions in 
C++ nicht teuer?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Heiko L. schrieb:
> In meinen Augen sind das tolle Toy-Examples.

Lass uns doch den Spaß.

Heiko L. schrieb:
> In c++ hat man für soetwas
> Exceptions parat.

An sich schon, aber auf Embedded-Targets haben die leider oft zu viel 
Overhead.

von Wilhelm M. (wimalopaan)


Lesenswert?

Niklas G. schrieb:
> Heiko L. schrieb:
>> In meinen Augen sind das tolle Toy-Examples.
>
> Lass uns doch den Spaß.
>
> Heiko L. schrieb:
>> In c++ hat man für soetwas
>> Exceptions parat.
>
> An sich schon, aber auf Embedded-Targets haben die leider oft zu viel
> Overhead.

Dies ist m.E. ein gutes Beispiel, um keine Exceptions zu verwenden. Denn 
das Zurückgeben der "leeren Menge" als Resultat ist (oder könnte 
zumindest) der Nornmalfall sein. (Zudem sind Exception der Tod jedes 
Optimizers).

von Heiko L. (zer0)


Lesenswert?

Stefanus F. schrieb:
> Sind Exceptions in
> C++ nicht teuer?

Ja, einige Größenordnungen langsamer als eine return-chain. Deswegen 
empfiehlt sich, Exception auch wirklich nur für "Ausnahmen" zu benutzen. 
In normalen Programmen daher besonders geeignet für Fehlerconditions, 
die eigentlich nicht auftreten sollten.
Es gäbe auch dort Argumente für eine return-chain, wie zB, dass man 
sonst ja nicht so schön die Paramter, die zum Fehler führten, loggen 
kann. :)

von Heiko L. (zer0)


Lesenswert?

Wilhelm M. schrieb:
> Dies ist m.E. ein gutes Beispiel, um keine Exceptions zu verwenden. Denn
> das Zurückgeben der "leeren Menge" als Resultat ist (oder könnte
> zumindest) der Nornmalfall sein. (Zudem sind Exception der Tod jedes
> Optimizers).

Also für mich sieht das initiale Beispiel des Codes so aus, als ob sich 
der OP eigentlich nicht um die Fehler kümmern will, die da zurück kommen 
könnten. Das ist erschreckend häufig der Fall - oft kann man einfach 
nichts vernünftiges tun. Beispiel: Man schreibt eine Datei und auf 
einmal ist die Festplatte voll. Wie oft kommt dieser Fall vor? Soll jede 
Funktion, die I/O betreibt, versuchen, diesen Fall zu handeln?

von Lars (Gast)


Lesenswert?

Hi,

eine Lösung mit switch ... case kommt nicht infrage?

Man könnte alle Fälle mit einer for Schleife durchgehen und in einem 
Ergebniss Vektor ablegen.
So würde man auch erkennen ob mehrer Funktionen ein Ergebniss 
zurückgeben und diese könnte man dann gegenüber stellen.

Viele Grüße

von Stefan F. (Gast)


Lesenswert?

Heiko L. schrieb:
> Es gäbe auch dort Argumente für eine return-chain, wie zB, dass man
> sonst ja nicht so schön die Paramter, die zum Fehler führten, loggen
> kann. :)

Im Bereich der Backend Anwendungen setzte ich da auf Ringpuffer. Jeder 
Threads bekommt einen Ringpuffer, der bis zu 100 Meldungen (ab Debug 
Level aufwärts) aufnimmt. Wenn ein Fehler auftritt, wird dieser zusammen 
mit den gepufferten Meldungen in eine Datei geschrieben.

Das kostet RAM und (typischerweise 1-5%) Performance, lohnt sich aber, 
denn so kann man Probleme viel besser analysieren. Vorausgesetzt, man 
geizt nicht mit Debug Meldungen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Heiko L. schrieb:
> Wilhelm M. schrieb:
>> Dies ist m.E. ein gutes Beispiel, um keine Exceptions zu verwenden. Denn
>> das Zurückgeben der "leeren Menge" als Resultat ist (oder könnte
>> zumindest) der Nornmalfall sein. (Zudem sind Exception der Tod jedes
>> Optimizers).
>
> Also für mich sieht das initiale Beispiel des Codes so aus, als ob sich
> der OP eigentlich nicht um die Fehler kümmern will, die da zurück kommen
> könnten.

Oh je. Das hoffe ich mal nicht.

Aber es müssen nicht unbedingt harte Fehler sein. Es kann auch z.B. eine 
Suchoperation sein, die nichts findet und deswegen eine leere Menge 
liefert.

von Heiko L. (zer0)


Lesenswert?

Stefanus F. schrieb:
> Heiko L. schrieb:
>> Es gäbe auch dort Argumente für eine return-chain, wie zB, dass man
>> sonst ja nicht so schön die Paramter, die zum Fehler führten, loggen
>> kann. :)
>
> Im Bereich der Backend Anwendungen setzte ich da auf Ringpuffer. Jeder
> Threads bekommt einen Ringpuffer, der bis zu 100 Meldungen (ab Debug
> Level aufwärts) aufnimmt. Wenn ein Fehler auftritt, wird dieser zusammen
> mit den gepufferten Meldungen in eine Datei geschrieben.
>
> Das kostet RAM und (typischerweise 1-5%) Performance, lohnt sich aber,
> denn so kann man Probleme viel besser analysieren. Vorausgesetzt, man
> geizt nicht mit Debug Meldungen.

Das glaube ich auf's Wort. Sas mit dem Logging ist nach meiner Erfahrung 
leider tragisch: Es ist immer zu wenig - auch wenn es zu viel ist.

von Heiko L. (zer0)


Lesenswert?

Wilhelm M. schrieb:
> Aber es müssen nicht unbedingt harte Fehler sein. Es kann auch z.B. eine
> Suchoperation sein, die nichts findet und deswegen eine leere Menge
> liefert.

Ja, da würde ich persönlich aber keine Exception werfen. Es sei denn ich 
wäre der Meinung, dass an der Stelle ein Ergebnis gefunden werden 
müsste. Das wäre dann ein logic_error.

von Wilhelm M. (wimalopaan)


Lesenswert?

Heiko L. schrieb:
> Wilhelm M. schrieb:
>> Aber es müssen nicht unbedingt harte Fehler sein. Es kann auch z.B. eine
>> Suchoperation sein, die nichts findet und deswegen eine leere Menge
>> liefert.
>
> Ja, da würde ich persönlich aber keine Exception werfen.

Ja, das sage ich doch!!!
Schau Dir meine Lösung an, da ist nichts mit Exceptions. std::optional 
ist eine generische Lösung, mit der man jeden Datentyp um die leere 
Menge / das Nichts erweitern kann (nullable types).

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Schau Dir meine Lösung an, da ist nichts mit Exceptions.

Man könnte sie aber leicht umbauen, dass sie bei Exceptions jeweils die 
nächste Funktion nimmt. Das würde eine lange 
try-catch-Kette/Verschachtelung ersparen (sofern man es als sinnvoll 
erachtet, das überhaupt mit Exceptions zu signalisieren; eigentlich 
sollte man nur sehr wenige catch-Blöcke haben). Das wäre dann aber nicht 
mehr constexpr ;-)

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Niklas G. schrieb:
> Wilhelm M. schrieb:
>> Schau Dir meine Lösung an, da ist nichts mit Exceptions.
>
> Man könnte sie aber leicht umbauen, dass sie bei Exceptions jeweils die
> nächste Funktion nimmt.

Warum sollte man das tun?

von Heiko L. (zer0)


Lesenswert?

Wilhelm M. schrieb:
> Ja, das sage ich doch!!!
> Schau Dir meine Lösung an, da ist nichts mit Exceptions. std::optional
> ist eine generische Lösung, mit der man jeden Datentyp um die leere
> Menge / das Nichts erweitern kann (nullable types).

Ja - und schau dir den Code des OP an. Da wird 3 mal nacheinander im 
Falle eines Fehlers nichts getan, als dieses Ergebnis weiter hoch zu 
reichen.
Und das setzt sich typischerweise noch 10-20 Stackframes weiter fort.
Genau dafür gibt es Exceptions. Da muss man dann auch nicht ganz oben 
rätseln, was denn der eigentliche Fehler wohl war: Diese Information 
verliert man mit Optionals. Und auch Result-Tupel wären nur sehr 
unwesentlich besser: Was soll denn der allgemeine Fehlertyp sein, den 
man ohne Verrenkungen oder Informationsverlust IMMER weitergeben kann? 
Errno?

EDIT: Das soll nicht heißen, dass der Compiler intern ein throw/catch 
nicht mit Result-Tupeln handeln sollte. :)

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Heiko L. schrieb:
> Wilhelm M. schrieb:
>> Ja, das sage ich doch!!!
>> Schau Dir meine Lösung an, da ist nichts mit Exceptions. std::optional
>> ist eine generische Lösung, mit der man jeden Datentyp um die leere
>> Menge / das Nichts erweitern kann (nullable types).
>
> Ja - und schau dir den Code des OP an. Da wird 3 mal nacheinander im
> Falle eines Fehlers nichts getan, als dieses Ergebnis weiter hoch zu
> reichen.

Und genau dafür habe ich m.E. bessere Lösung.

> Und das setzt sich typischerweise noch 10-20 Stackframes weiter fort.

Woher weißt Du das?

> Genau dafür gibt es Exceptions. Da muss man dann auch nicht ganz oben
> rätseln, was denn der eigentliche Fehler wohl war: Diese Information
> verliert man mit Optionals.

Das ist auch nicht der Zweck von optional (s.o.).

>Und auch Result-Tupel wären nur sehr
> unwesentlich besser:

Variant wäre besser.

von Heiko L. (zer0)


Lesenswert?

Wilhelm M. schrieb:
> Heiko L. schrieb:
>> Wilhelm M. schrieb:
>>> Ja, das sage ich doch!!!
>>> Schau Dir meine Lösung an, da ist nichts mit Exceptions. std::optional
>>> ist eine generische Lösung, mit der man jeden Datentyp um die leere
>>> Menge / das Nichts erweitern kann (nullable types).
>>
>> Ja - und schau dir den Code des OP an. Da wird 3 mal nacheinander im
>> Falle eines Fehlers nichts getan, als dieses Ergebnis weiter hoch zu
>> reichen.
>
> Und genau dafür habe ich m.E. bessere Lösung.

Ja, finde ich nicht.


>> Und das setzt sich typischerweise noch 10-20 Stackframes weiter fort.
>
> Woher weißt Du das?

Konkret? Gar nicht. Woher weißt du, dass es nicht so ist?

>> Genau dafür gibt es Exceptions. Da muss man dann auch nicht ganz oben
>> rätseln, was denn der eigentliche Fehler wohl war: Diese Information
>> verliert man mit Optionals.
>
> Das ist auch nicht der Zweck von optional (s.o.).

Richtig. Deswegen sind die auch ungeeignet für Fehlerbehandlungen.

>>Und auch Result-Tupel wären nur sehr
>> unwesentlich besser:
>
> Variant wäre besser.

Nicht echt. Was ist der allgemeine Fehlertyp?

von Wilhelm M. (wimalopaan)


Lesenswert?

Heiko L. schrieb:
> Wilhelm M. schrieb:
>> Heiko L. schrieb:
>>> Wilhelm M. schrieb:
>>>> Ja, das sage ich doch!!!
>>>> Schau Dir meine Lösung an, da ist nichts mit Exceptions. std::optional
>>>> ist eine generische Lösung, mit der man jeden Datentyp um die leere
>>>> Menge / das Nichts erweitern kann (nullable types).
>>>
>>> Ja - und schau dir den Code des OP an. Da wird 3 mal nacheinander im
>>> Falle eines Fehlers nichts getan, als dieses Ergebnis weiter hoch zu
>>> reichen.
>>
>> Und genau dafür habe ich m.E. bessere Lösung.
>
> Ja, finde ich nicht.

Es geht hier m.E. gar nicht um Fehlerbehandlung. Mein Ansatz war (s.o.), 
dass der DT `int` keine ungültigen Wert besitzt. Es wird hier 
willkürlich der Wert 0 missbraucht, um die leere Menge darzustellen. 
Dafür braucht man nullable-types. Die primitiven DT ausser raw pointer 
sidn nicht nullable. Genau diese Eigenschaft fügt optional hinzu.

Der zweite Ansatz war es, eine generische Lösung zu liefern.

Mit Deiner Forderung nach Exceptions machst Du ein ganz andere Fass auf. 
Fehlerbehandlung und nullable-Types sind unterschiedliche Sachen.

Fehlerbehandlung kann man mit Exceptions oder ohne Exceptions machen 
(wobei exception-safety in C++ ja ein besonderes Thema ist), dass will 
ich gar nicht vorschreiben (wobei die Tendenz ganz klar in Richtung 
no-exceptions geht, weil man erkannt hat, dass exceptions auch nicht das 
Gelbe vom Ei sind, und dies ein Kapitel, was in C++ problematisch ist, 
zumindest wenn man sich vielen existierenden Code ansieht).

von Heiko L. (zer0)


Lesenswert?

Wilhelm M. schrieb:
> Es geht hier m.E. gar nicht um Fehlerbehandlung. Mein Ansatz war (s.o.),
> dass der DT `int` keine ungültigen Wert besitzt. Es wird hier
> willkürlich der Wert 0 missbraucht, um die leere Menge darzustellen.

Richtig. Dafür wären Optionals durchaus brauchbar.

Wilhelm M. schrieb:
> Der zweite Ansatz war es, eine generische Lösung zu liefern.

Ja - und das mag hier konkret irgendwie genügen. Es ist aber kein 
allgemein gangbarer Weg. Wenn man den return-Wert aus einer Struct 
auslesen müsste oder auch nur einen int casten wäre man wieder genau am 
Anfang.
Ebenso, wenn man irgenwo noch einen anderen Parameter braucht.
Also ist diese Lösung - entschuldigung! - in meinen Augen ein 
Toy-Example, dass für den konkreten Einzelfall viel zu aufwändig und im 
Allgemeinen unbrauchbar ist.
Mit lambdas käme man einen Schritt weiter:
1
auto z = first_of(
2
 []{ return x(1); },
3
 []{ return y(2).member; },
4
);

Aber auch da würde ich mich nicht an Optionals binden. Generischen Code 
schreibt man, indem man sich konzeptionell bindet :)

von Jemand (Gast)


Angehängte Dateien:

Lesenswert?

Niklas G. schrieb:
> Wilhelm M. schrieb:
>> genau! Deswegen sollte man eben möglichst domänen-spezifische Datentypen
>> kreieren und den Code möglichst generisch halten.
>
> Darauf hab ich gewartet :) Aus Spaß im Anhang eine aufgetunte Version
> davon.

Hab das mal aus Spaß in D nachgebaut.

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.