c: Wie multiple return-Statements vermeiden?

Gast #5910368
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.
#5910378
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.
Gast #5910381
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.
Gast #5910390
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
}
Gast #5910391
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.
Persönliche Seite #5910394
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.
Gast #5910398
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.
Moderator Persönliche Seite #5910400
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
Gast #5910413
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);
#5910428
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.
Gast #5910437
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.
#5910489
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.
#5910500
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.
#5910509
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
Gast #5910513
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.
Moderator Persönliche Seite #5910520
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. ;-)
Gast #5910523
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.
#5910525
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.
Moderator Persönliche Seite #5910534
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.
#5910537
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.
Moderator Persönliche Seite #5910552
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.
#5910557
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.
Gast #5910566
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.
Moderator Persönliche Seite #5910568
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.
Moderator Persönliche Seite #5910572
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?
Gast #5910578
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.
#5910579
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
}
Gast #5910582
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
}
Moderator Persönliche Seite #5910592
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.
Gast #5910601
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!
Gast #5910604
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
}
Persönliche Seite #5910615
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.
(Firma: @home) #5910630
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
#5910631
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)
#5910636
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.
Gast #5910661
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.
Gast #5910662
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.
Moderator #5910701
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
Gast #5910726
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
#5910741
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(); }
Gast #5910758
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.
Gast #5910864
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!
#5910887
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.
Gast #5910914
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;}
Gast #5910916
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.
Gast #5910991
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.
#5911214
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.
Gast #5911222
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.
Moderator #5911411
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
                                     }
#5911424
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
Moderator #5911434
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.
#5911446
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!
Gast #5911454
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.
Gast #5911462
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.
Moderator #5911472
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 ;-)
#5911488
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.
#5911590
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.
Moderator #5911823
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.
#5911868
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.
Gast #5911869
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.
#5911928
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.
#5911939
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.
Gast #5912128
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.
Gast #5912136
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...
#5912157
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?
Gast #5912228
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.
Moderator #5912360
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 :)
Gast #5912401
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.
Gast #5912405
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
Moderator #5912421
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.
Gast #5912440
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...
Persönliche Seite #5912677
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
Angehängte Dateien:
#5912701
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".
#5912715
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).
#5912716
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. :)
#5912724
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?
Gast #5912727
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
Gast #5912738
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.
#5912746
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.
#5912750
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.
#5912753
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.
#5912758
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).
Persönliche Seite #5912761
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 ;-)
#5912767
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. :)
#5912777
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.
#5912779
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?
#5912796
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).
#5912811
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 :)
Gast #5912817
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.
Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren