Perché C ++ 11 non supporta elenchi di inizializzatori designati come C99? [chiuso]


121

Tener conto di:

struct Person
{
    int height;
    int weight;
    int age;
};

int main()
{
    Person p { .age = 18 };
}

Il codice sopra è legale in C99, ma non legale in C ++ 11.

Che cos 'era il la logica del comitato standard per escludere il supporto per una funzione così utile?


10
Apparentemente non aveva senso per il comitato di progettazione includerlo, o semplicemente non è emerso nelle riunioni. Vale la pena notare che gli inizializzatori designati C99 non sono in nessuna delle versioni della specifica C ++. I costruttori sembrano essere il costrutto di inizializzazione preferito, e per una buona ragione: garantiscono un'inizializzazione coerente degli oggetti, se vengono scritti correttamente.
Robert Harvey

19
Il tuo ragionamento è arretrato, una lingua non ha bisogno di una motivazione per non avere una caratteristica, ha bisogno di una logica per averne una e una forte in quella. Il C ++ è abbastanza gonfio, così com'è.
Matthieu M.

42
Una buona ragione (che non può essere risolta con i costruttori se non scrivendo wrapper stupefacenti) è che, indipendentemente dal fatto che si usi o meno C ++, la maggior parte delle API reali sono C, non C ++, e non poche di esse ti fanno fornire una struttura in cui vuoi impostare uno o due campi - e non necessariamente il primo - ma devono avere il resto inizializzato a zero. L'API Win32 OVERLAPPEDè un tale esempio. Essere in grado di scrivere ={.Offset=12345};renderebbe il codice molto più chiaro (e probabilmente meno soggetto a errori). I socket BSD sono un esempio simile.
Damon

14
Il codice in mainC99 non è legale. Dovrebbe leggere struct Person p = { .age = 18 };
chqrlie

14
FYI C ++ 20 supporterà inizializzatori designati
Andrew Tomazos

Risposte:


34

C ++ ha costruttori. Se ha senso inizializzare un solo membro, questo può essere espresso nel programma implementando un costruttore appropriato. Questo è il tipo di astrazione che C ++ promuove.

D'altra parte, la funzionalità degli inizializzatori designati riguarda maggiormente l'esposizione e il facile accesso ai membri direttamente nel codice client. Questo porta a cose come avere una persona di 18 anni (anni?) Ma con altezza e peso pari a zero.


In altre parole, gli inizializzatori designati supportano uno stile di programmazione in cui gli interni sono esposti e al client viene data la flessibilità di decidere come desidera utilizzare il tipo.

Il C ++ è invece più interessato a mettere la flessibilità dalla parte del designer di un tipo, quindi i designer possono rendere facile usare un tipo correttamente e difficile da usare in modo errato. Dare al designer il controllo su come un tipo può essere inizializzato è parte di questo: il designer determina i costruttori, gli inizializzatori in classe, ecc.


12
Mostra un link di riferimento per quello che dici è il motivo per cui C ++ non ha inizializzatori designati. Non ricordo di aver mai visto la proposta.
Johannes Schaub - litb

20
Non è proprio la ragione per non fornire un costruttore Personche il suo autore volesse fornire la massima flessibilità possibile agli utenti per impostare e inizializzare i membri? L'utente può anche già scrivere Person p = { 0, 0, 18 };(e per buoni motivi).
Johannes Schaub - litb

7
Qualcosa di simile è stato recentemente accettato nelle specifiche C ++ 14 da open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.html .
Johannes Schaub - litb

4
@ JohannesSchaub-litb Non sto parlando della causa puramente meccanica e prossima (cioè, non è stata proposta al comitato). Sto descrivendo quello che credo sia il fattore dominante. - Personha un design molto C quindi le caratteristiche C possono avere senso. Tuttavia C ++ probabilmente consente una progettazione migliore che evita anche la necessità di inizializzatori designati. - A mio avviso, rimuovere la restrizione sugli inizializzatori in classe per gli aggregati è molto più in linea con l'etica del C ++ rispetto agli inizializzatori designati.
bames53

4
La sostituzione C ++ per questo potrebbe essere denominata argomenti di funzione. Ma al momento, gli argomenti sui nomi non esistono ufficialmente. Vedere N4172 Argomenti nominati per una proposta di questo. Renderebbe il codice meno soggetto a errori e più facile da leggere.
David Baird

89

Il 15 luglio 17 P0329R4 è stato accettato nelstandard: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
Questo porta un supporto limitato perInizializzatori designati di. Questa limitazione è descritta come segue da C.1.7 [diff.decl] .4, dato:

struct A { int x, y; };
struct B { struct A a; };

Le seguenti inizializzazioni designate, valide in C, sono limitate in C ++:

  • struct A a = { .y = 1, .x = 2 } non è valido in C ++ perché i designatori devono apparire nell'ordine di dichiarazione dei membri dati
  • int arr[3] = { [1] = 5 } non è valido in C ++ perché l'inizializzazione designata dall'array non è supportata
  • struct B b = {.a.x = 0} non è valido in C ++ perché i designatori non possono essere annidati
  • struct A c = {.x = 1, 2} non è valido in C ++ perché tutti o nessuno dei membri di dati deve essere inizializzato dai designatori

Per e in precedenza Boost lo ha effettivamente versioni supportano inizializzatori designati e sono state presentate numerose proposte per aggiungere supporto astandard, ad esempio: n4172 e la proposta di Daryle Walker di aggiungere una designazione agli inizializzatori . Le proposte citano l'attuazione diGli inizializzatori designati di Visual C ++, gcc e Clang affermano:

Riteniamo che le modifiche saranno relativamente semplici da implementare

Ma il comitato standard respinge ripetutamente tali proposte , affermando:

EWG ha riscontrato vari problemi con l'approccio proposto e non ha ritenuto fattibile provare a risolvere il problema, poiché è stato provato molte volte e ogni volta ha fallito

I commenti di Ben Voigt mi hanno aiutato a vedere i problemi insormontabili con questo approccio; dato:

struct X {
    int c;
    char a;
    float b;
};

In quale ordine dovrebbero essere chiamate queste funzioni : struct X foo = {.a = (char)f(), .b = g(), .c = h()} ? Sorprendentemente, in:

L'ordine di valutazione delle sottoespressioni in qualsiasi inizializzatore è in sequenza indeterminata [ 1 ]

(Visual C ++, gcc e Clang sembrano avere un comportamento concordato in quanto effettueranno tutte le chiamate in questo ordine :)

  1. h()
  2. f()
  3. g()

Ma la natura indeterminata dello standard significa che se queste funzioni avessero un'interazione, anche lo stato del programma risultante sarebbe indeterminato e il compilatore non ti avviserebbe : c'è un modo per essere avvisati del comportamento anomalo degli inizializzatori designati?

non hanno requisiti rigorosi di inizializzazione-list 11.6.4 [dcl.init.list] 4:

All'interno della lista di inizializzatori di una lista di inizializzazione con parentesi graffe, le clausole di inizializzazione, comprese quelle risultanti dalle espansioni di pacchetto (17.5.3), vengono valutate nell'ordine in cui appaiono. Cioè, ogni calcolo di valore ed e ff etto laterale associato a una data clausola di inizializzazione viene sequenziato prima di ogni calcolo di valore ed effetto collaterale associato a qualsiasi clausola di inizializzazione che lo segue nell'elenco separato da virgole dell'elenco di inizializzatori.

Così il supporto avrebbe richiesto che fosse eseguito nell'ordine:

  1. f()
  2. g()
  3. h()

Rompere la compatibilità con il precedente implementazioni.
Come discusso in precedenza, questo problema è stato aggirato dalle limitazioni sugli inizializzatori designati accettati. Forniscono un comportamento standardizzato, garantendo l'ordine di esecuzione degli inizializzatori designati.


3
Certo, in questo codice: struct X { int c; char a; float b; }; X x = { .a = f(), .b = g(), .c = h() };la chiamata a h()viene eseguita prima di f()o g(). Se la definizione di struct Xnon è vicina, sarà molto sorprendente. Ricorda che le espressioni di inizializzazione non devono essere prive di effetti collaterali.
Ben Voigt

2
Ovviamente, questa non è una novità, l'inizializzazione del membro ctor presenta già questo problema, ma è nella definizione di membro di classe, quindi l'accoppiamento stretto non è una sorpresa. E gli inizializzatori designati non possono fare riferimento agli altri membri come possono fare gli inizializzatori di membri ctor.
Ben Voigt

2
@ MattMcNabb: No, non è più estremo. Ma ci si aspetta che lo sviluppatore che implementa il costruttore della classe conosca l'ordine di dichiarazione dei membri. Considerando che il consumatore della classe potrebbe essere un programmatore completamente diverso. Poiché il punto è consentire l'inizializzazione senza dover cercare l'ordine dei membri, questo sembra un difetto fatale nella proposta. Poiché gli inizializzatori designati non possono fare riferimento all'oggetto da costruire, la prima impressione è che le espressioni di inizializzazione possano essere valutate prima, in ordine di designazione, quindi l'inizializzazione dei membri in ordine di dichiarazione. Ma ...
Ben Voigt

2
@ JonathanMee: Beh, l'altra domanda ha risposto che ... Gli inizializzatori aggregati C99 non sono ordinati, quindi non ci sono aspettative che vengano ordinati gli inizializzatori designati. Gli elenchi di inizializzazione con parentesi graffe C ++ SONO ordinati e la proposta per gli inizializzatori designati utilizza un ordine potenzialmente sorprendente (non puoi essere coerente sia con l'ordine lessicale, usato per tutti gli elenchi con inizializzazione con parentesi graffa, sia con l'ordine dei membri, usato per l'inizializzatore ctor -liste)
Ben Voigt

3
Jonathan: "Il supporto di c ++ avrebbe richiesto che fosse eseguito nell'ordine [...] Rompendo la compatibilità con le precedenti implementazioni di c99." Questo non lo capisco, mi dispiace. 1. Se l'ordine è indeterminato in C99, ovviamente qualsiasi ordine effettivo dovrebbe andare bene, inclusa qualsiasi scelta arbitraria C ++. b) Non supportare il des. gli inizializzatori del tutto infrangono già di più la compatibilità con C99 ...
Sz.

34

Un po 'di hackery, quindi condividi solo per divertimento.

#define with(T, ...)\
    ([&]{ T ${}; __VA_ARGS__; return $; }())

E usalo come:

MyFunction(with(Params,
    $.Name = "Foo Bar",
    $.Age  = 18
));

che si espande a:

MyFunction(([&] {
 Params ${};
 $.Name = "Foo Bar", $.Age = 18;
 return $;
}()));

Neat, crea un lambda con una variabile denominata $di tipo Te tu assegni i suoi membri direttamente prima di restituirlo. Nifty. Mi chiedo se ci siano problemi di prestazioni con esso.
TankorSmash

1
In una build ottimizzata non si vedono tracce del lambda né della sua invocazione. È tutto inline.
keebus

1
Adoro questa risposta.
Seph Reed

6
Woah. Non sapevo nemmeno che $ fosse un nome valido.
Chris Watts

Era supportato dai compilatori C legacy e il supporto è rimasto per la compatibilità con le versioni precedenti.
keebus

22

Gli inizializzatori designati sono attualmente inclusi nel corpo di lavoro di C ++ 20: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf così potremmo finalmente vederli!


3
Si noti che sono limitati: in C ++, il supporto di inizializzazione designato è limitato rispetto alla funzionalità corrispondente in C.In C ++, i designatori per i membri di dati non statici devono essere specificati nell'ordine di dichiarazione, i designatori per gli elementi di matrice e i designatori nidificati non lo sono supportati, e gli inizializzatori designati e non designati non possono essere combinati nello stesso elenco di inizializzatori. Ciò significa che, in particolare, non sarai ancora in grado di creare facilmente una tabella di ricerca con chiave enum .
Ruslan,

@Ruslan: mi chiedo perché C ++ li abbia limitati così tanto? Capisco che potrebbe esserci confusione sul fatto che l'ordine in cui i valori degli elementi vengono valutati e / o scritti nella struttura corrisponda all'ordine in cui gli elementi sono specificati nell'elenco di inizializzazione o l'ordine in cui i membri appaiono nella struttura, ma il la soluzione a ciò sarebbe semplicemente dire che le espressioni di inizializzazione vengono eseguite in sequenza arbitraria e la durata dell'oggetto non inizia fino al completamento dell'inizializzazione (l' &operatore restituirà l'indirizzo che l'oggetto avrà durante la sua vita).
supercat

5

Due caratteristiche principali di C99 che mancano a C ++ 11 menzionano "inizializzatori designati e C ++".

Penso che l '"inizializzatore designato" sia correlato alla potenziale ottimizzazione. Qui uso "gcc / g ++" 5.1 come esempio.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>    
struct point {
    int x;
    int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

Sapevamo al momento della compilazione, a_point.xè zero, quindi potevamo aspettarci che foofosse ottimizzato in un unico printf.

$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
   0x00000000004004f0 <+0>: sub    $0x8,%rsp
   0x00000000004004f4 <+4>: mov    $0x4005bc,%edi
   0x00000000004004f9 <+9>: xor    %eax,%eax
   0x00000000004004fb <+11>:    callq  0x4003a0 <printf@plt>
   0x0000000000400500 <+16>:    xor    %eax,%eax
   0x0000000000400502 <+18>:    add    $0x8,%rsp
   0x0000000000400506 <+22>:    retq   
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc:   "x == 0"

fooè ottimizzato per la x == 0sola stampa .

Per la versione C ++,

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
    point(int _x,int _y):x(_x),y(_y){}
    int x;
    int y;
};
const struct point a_point(0,0);
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

E questo è l'output del codice di assemblaggio ottimizzato.

g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>:    push   %rbx
0x00000000004005c1 <+1>:    mov    0x200489(%rip),%ebx        # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>:    test   %ebx,%ebx
0x00000000004005c9 <+9>:    je     0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>:   mov    $0x1,%ebx
0x00000000004005d0 <+16>:   mov    $0x4006a3,%edi
0x00000000004005d5 <+21>:   xor    %eax,%eax
0x00000000004005d7 <+23>:   callq  0x400460 <printf@plt>
0x00000000004005dc <+28>:   mov    %ebx,%eax
0x00000000004005de <+30>:   pop    %rbx
0x00000000004005df <+31>:   retq   
0x00000000004005e0 <+32>:   mov    $0x40069c,%edi
0x00000000004005e5 <+37>:   xor    %eax,%eax
0x00000000004005e7 <+39>:   callq  0x400460 <printf@plt>
0x00000000004005ec <+44>:   mov    %ebx,%eax
0x00000000004005ee <+46>:   pop    %rbx
0x00000000004005ef <+47>:   retq   

Possiamo vedere che a_pointnon è realmente un valore di costante del tempo di compilazione.


8
Ora per favore prova constexpr point(int _x,int _y):x(_x),y(_y){}. L'ottimizzatore di clang ++ sembra eliminare anche il confronto nel codice. Quindi, questo è solo un problema di QoI.
giorno

Mi aspetto anche che l'intero oggetto a_point venga ottimizzato se avesse un collegamento interno. cioè mettilo nello spazio dei nomi anonimo e guarda cosa succede. goo.gl/wNL0HC
Arvid

@dyp: anche solo la definizione di un costruttore è possibile solo se il tipo è sotto il tuo controllo. Non puoi farlo, ad esempio, per struct addrinfoo struct sockaddr_in, quindi ti rimangono incarichi separati dalle dichiarazioni.
musiphil

2
@musiphil Almeno in C ++ 14, quelle strutture in stile C possono essere impostate correttamente in una funzione constexpr come variabili locali utilizzando l'assegnazione e quindi restituite da quella funzione. Inoltre, il mio punto non era quello di mostrare un'implementazione alternativa del costruttore in C ++ che consente l'ottimizzazione, ma mostrare che è possibile per il compilatore eseguire questa ottimizzazione se la forma di inizializzazione è diversa. Se il compilatore è "abbastanza buono" (cioè supporta questa forma di ottimizzazione), allora dovrebbe essere irrilevante se si utilizza un ctor o inizializzatori designati, o qualcos'altro.
giorno
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.