Domanda

C ++ 11 introduce valori letterali definiti dall'utente che consentiranno l'introduzione di una nuova sintassi letterale basata su esistenti valori letterali ( int , hex , string , float ) in modo che qualsiasi tipo possa avere una presentazione letterale .

Esempi:

// imaginary numbers
std::complex<long double> operator "" _i(long double d) // cooked form
{ 
    return std::complex<long double>(0, d); 
}
auto val = 3.14_i; // val = complex<long double>(0, 3.14)

// binary values
int operator "" _B(const char*); // raw form
int answer = 101010_B; // answer = 42

// std::string
std::string operator "" _s(const char* str, size_t /*length*/) 
{ 
    return std::string(str); 
}

auto hi = "hello"_s + " world"; // + works, "hello"_s is a string not a pointer

// units
assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

A prima vista sembra molto bello, ma mi chiedo quanto sia realmente applicabile, quando ho provato a pensare di avere le date _AD e _BC per creare date I trovato problematico a causa dell'ordine dell'operatore. 1974/01 / 06_AD valuta innanzitutto 1974/01 (come int ) e solo successivamente il 06_AD (per non parlare di agosto e settembre che devono essere scritti senza 0 per motivi ottali). Questo può essere aggirato facendo in modo che la sintassi sia 1974-1 / 6_AD in modo che l'ordine di valutazione dell'operatore funzioni ma è goffo.

Quindi la mia domanda si riduce a questo: credi che questa funzionalità si giustificherà? Quali altri letterali vorresti definire che renderanno più leggibile il tuo codice C ++?


Sintassi aggiornata per adattarsi alla bozza finale di giugno 2011

È stato utile?

Soluzione

Ecco un caso in cui vi è un vantaggio nell'utilizzare valori letterali definiti dall'utente anziché una chiamata del costruttore:

#include <bitset>
#include <iostream>

template<char... Bits>
  struct checkbits
  {
    static const bool valid = false;
  };

template<char High, char... Bits>
  struct checkbits<High, Bits...>
  {
    static const bool valid = (High == '0' || High == '1')
                   && checkbits<Bits...>::valid;
  };

template<char High>
  struct checkbits<High>
  {
    static const bool valid = (High == '0' || High == '1');
  };

template<char... Bits>
  inline constexpr std::bitset<sizeof...(Bits)>
  operator"" _bits() noexcept
  {
    static_assert(checkbits<Bits...>::valid, "invalid digit in binary string");
    return std::bitset<sizeof...(Bits)>((char []){Bits..., '\0'});
  }

int
main()
{
  auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits;
  std::cout << bits << std::endl;
  std::cout << "size = " << bits.size() << std::endl;
  std::cout << "count = " << bits.count() << std::endl;
  std::cout << "value = " << bits.to_ullong() << std::endl;

  //  This triggers the static_assert at compile time.
  auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits;

  //  This throws at run time.
  std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101_bits");
}

Il vantaggio è che un'eccezione di runtime viene convertita in un errore di compilazione. Non è stato possibile aggiungere l'asserzione statica al ctor del bitset prendendo una stringa (almeno non senza argomenti del modello di stringa).

Altri suggerimenti

A prima vista, sembra essere un semplice zucchero sintattico.

Ma quando guardiamo più in profondità, vediamo che è più che zucchero sintattico, poiché estende le opzioni dell'utente C ++ per creare tipi definiti dall'utente che si comportano esattamente come tipi predefiniti distinti. In questo, questo piccolo "bonus" è un'aggiunta C ++ 11 molto interessante a C ++.

Ne abbiamo davvero bisogno in C ++?

Vedo pochi usi nel codice che ho scritto negli anni passati, ma solo perché non l'ho usato in C ++ non significa che non sia interessante per un altro sviluppatore C ++ .

Avevamo usato in C ++ (e in C, immagino), valori letterali definiti dal compilatore, per digitare numeri interi come numeri interi corti o lunghi, numeri reali come float o double (o anche double long) e stringhe di caratteri normali o caratteri ampi.

In C ++, abbiamo avuto la possibilità di creare i nostri tipi (ad es. classi), potenzialmente senza spese generali (inline, ecc.). Abbiamo avuto la possibilità di aggiungere operatori ai loro tipi, per farli comportare come tipi incorporati simili, il che consente agli sviluppatori C ++ di utilizzare matrici e numeri complessi nel modo più naturale che avrebbero se fossero stati aggiunti al linguaggio stesso. Possiamo anche aggiungere operatori di cast (che di solito è una cattiva idea, ma a volte è la soluzione giusta).

Abbiamo ancora perso una cosa: i tipi di utenti si comportano come tipi predefiniti: valori letterali definiti dall'utente.

Quindi, suppongo sia un'evoluzione naturale per la lingua, ma per essere il più completo possibile: " Se vuoi creare un tipo e vuoi che si comporti il ??più possibile come un built-in tipi, ecco gli strumenti ... "

Immagino che sia molto simile alla decisione di .NET di trasformare ogni primitiva in una struttura, inclusi booleani, numeri interi, ecc. e che tutte le strutture derivino da Object. Questa decisione da sola pone .NET molto al di fuori della portata di Java quando si lavora con le primitive, non importa quanti hack di boxe / unboxing Java aggiungerà alle sue specifiche.

Ne hai davvero bisogno in C ++?

Questa domanda è per TU rispondere. Non Bjarne Stroustrup. Non Herb Sutter. Non qualunque sia il membro del comitato standard C ++. Questo è il motivo per cui hai la scelta in C ++ e non limiteranno una notazione utile ai soli tipi incorporati.

Se tu ne hai bisogno, allora è una gradita aggiunta. Se tu non lo fai, beh ... Non usarlo. Non ti costerà nulla.

Benvenuto in C ++, la lingua in cui le funzionalità sono opzionali.

Mastodontiche ??? Fammi vedere i tuoi complessi !!!

C'è una differenza tra gonfio e complesso (gioco di parole intenzionale).

Come mostrato da Niels su Quali nuove funzionalità aggiungono letterali definiti dall'utente al C ++? , essere in grado di scrivere un numero complesso è una delle due funzionalità aggiunte" recentemente ". a C e C ++:

// C89:
MyComplex z1 = { 1, 2 } ;

// C99: You'll note I is a macro, which can lead
// to very interesting situations...
double complex z1 = 1 + 2*I;

// C++:
std::complex<double> z1(1, 2) ;

// C++11: You'll note that "i" won't ever bother
// you elsewhere
std::complex<double> z1 = 1 + 2_i ;

Ora, sia C99 "doppio complesso" tipo e C ++ "std :: complex" i tipi possono essere moltiplicati, aggiunti, sottratti, ecc. utilizzando il sovraccarico dell'operatore.

Ma in C99, hanno appena aggiunto un altro tipo come tipo incorporato e il supporto di sovraccarico dell'operatore integrato. E hanno aggiunto un'altra funzione letterale integrata.

In C ++, hanno appena usato le funzionalità esistenti del linguaggio, hanno visto che l'elemento letterale era una naturale evoluzione del linguaggio e quindi l'hanno aggiunto.

In C, se hai bisogno dello stesso miglioramento della notazione per un altro tipo, sei sfortunato fino a quando fai pressioni per aggiungere le tue funzioni di onda quantistica (o punti 3D o qualsiasi tipo di base che stai usando nel tuo campo di lavoro ) allo standard C poiché un tipo incorporato ha esito positivo.

In C ++ 11, puoi farlo da solo:

Point p = 25_x + 13_y + 3_z ; // 3D point

È gonfio? No , la necessità è presente, come dimostrato da come entrambi i complessi C e C ++ necessitano di un modo per rappresentare i loro valori complessi letterali.

È stato progettato in modo errato? No , è progettato come ogni altra funzionalità C ++, con in mente l'estensibilità.

È solo a scopo di notazione? No , in quanto può persino aggiungere la sicurezza dei tipi al tuo codice.

Ad esempio, immaginiamo un codice orientato ai CSS:

css::Font::Size p0 = 12_pt ;       // Ok
css::Font::Size p1 = 50_percent ;  // Ok
css::Font::Size p2 = 15_px ;       // Ok
css::Font::Size p3 = 10_em ;       // Ok
css::Font::Size p4 = 15 ;         // ERROR : Won't compile !

È quindi molto semplice applicare una tipizzazione forte all'assegnazione dei valori.

È pericoloso?

Buona domanda. Queste funzioni possono avere lo spazio dei nomi? Se sì, allora Jackpot!

Ad ogni modo, come tutto, puoi ucciderti se uno strumento viene usato in modo improprio . C è potente e puoi sparare la testa se usi la pistola C. C ++ ha la pistola C, ma anche il bisturi, il taser e qualsiasi altro strumento che troverai nel toolkit. Puoi abusare del bisturi e sanguinarti fino alla morte. Oppure puoi creare un codice molto elegante e robusto.

Quindi, come ogni funzione C ++, ne hai davvero bisogno? È la domanda a cui devi rispondere prima di usarlo in C ++. Se non lo fai, non ti costerà nulla. Ma se ne hai davvero bisogno, almeno, la lingua non ti deluderà.

L'esempio della data?

Il tuo errore, a mio avviso, è che stai mescolando gli operatori:

1974/01/06AD
    ^  ^  ^

Questo non può essere evitato, poiché / essendo un operatore, il compilatore deve interpretarlo. E, AFAIK, è una buona cosa.

Per trovare una soluzione al tuo problema, scriverei il letterale in qualche altro modo. Ad esempio:

"1974-01-06"_AD ;   // ISO-like notation
"06/01/1974"_AD ;   // french-date-like notation
"jan 06 1974"_AD ;  // US-date-like notation
19740106_AD ;       // integer-date-like notation

Personalmente, sceglierei le date intere e ISO, ma dipende dalle VOSTRE esigenze. Qual è il punto centrale di consentire all'utente di definire i propri nomi letterali.

È molto carino per il codice matematico. Dalla mia mente posso vedere l'uso per i seguenti operatori:

deg per gradi. Ciò rende la scrittura degli angoli assoluti molto più intuitiva.

double operator ""_deg(long double d)
{ 
    // returns radians
    return d*M_PI/180; 
}

Può anche essere usato per varie rappresentazioni in virgola fissa (che sono ancora in uso nel campo del DSP e della grafica).

int operator ""_fix(long double d)
{ 
    // returns d as a 1.15.16 fixed point number
    return (int)(d*65536.0f); 
}

Sembrano buoni esempi su come usarlo. Aiutano a rendere più leggibili le costanti nel codice. È un altro strumento per rendere illeggibile anche il codice, ma abbiamo già così tanti strumenti che abusano che uno in più non fa molto male.

Le UDL sono spaziate dai nomi (e possono essere importate usando dichiarazioni / direttive, ma non puoi esplicitamente inserire nello spazio dei nomi un letterale come 3.14std :: i ), il che significa che lì (si spera) non ci sarà tonnellate di scontri.

Il fatto che possano effettivamente essere templati (e constexpr'd) significa che puoi fare cose piuttosto potenti con le UDL. Gli autori di Bigint saranno davvero felici, poiché possono finalmente avere costanti arbitrariamente grandi, calcolate al momento della compilazione (tramite constexpr o template).

Sono solo triste che non vedremo un paio di letterali utili nello standard (dall'aspetto di esso), come s per std :: string e i per l'unità immaginaria.

La quantità di tempo di codifica che verrà salvata dagli UDL non è in realtà così elevata, ma la leggibilità sarà notevolmente aumentata e sempre più calcoli possono essere spostati in tempo di compilazione per un'esecuzione più rapida.

Vorrei aggiungere un po 'di contesto. Per il nostro lavoro, i letterali definiti dall'utente sono molto necessari. Lavoriamo su MDE (Model-Driven Engineering). Vogliamo definire modelli e metamodelli in C ++. Abbiamo effettivamente implementato una mappatura da Ecore a C ++ ( EMF4CPP ).

Il problema si presenta quando si è in grado di definire gli elementi del modello come classi in C ++. Stiamo adottando l'approccio di trasformare il metamodello (Ecore) in modelli con argomenti. Gli argomenti del modello sono le caratteristiche strutturali di tipi e classi. Ad esempio, una classe con due attributi int sarebbe qualcosa del tipo:

typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;

Tuttavia, si scopre che ogni elemento in un modello o metamodello, di solito ha un nome. Vorremmo scrivere:

typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;

MA, C ++ o C ++ 0x non lo consentono, poiché le stringhe sono vietate come argomenti per i modelli. Puoi scrivere il nome char con char, ma questo è sicuramente un casino. Con letterali definiti dall'utente, potremmo scrivere qualcosa di simile. Supponiamo di usare " _n " per identificare i nomi degli elementi del modello (non uso l'esatta sintassi, solo per fare un'idea):

typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;

Infine, avere quelle definizioni come modelli ci aiuta molto a progettare algoritmi per attraversare gli elementi del modello, le trasformazioni del modello, ecc. che sono davvero efficienti, perché le informazioni sul tipo, l'identificazione, le trasformazioni, ecc. sono determinate dal compilatore al momento della compilazione tempo.

Bjarne Stroustrup parla di UDL in questo C + +11 talk , nella prima sezione sulle interfacce ricche di tipi, circa 20 minuti.

Il suo argomento di base per le UDL assume la forma di un sillogismo:

  1. " Trivial " i tipi, ovvero i tipi primitivi incorporati, possono solo rilevare errori di tipo banali. Le interfacce con tipi più ricchi consentono al sistema dei tipi di rilevare più tipi di errori.

  2. I tipi di errori di tipo che il codice riccamente digitato può rilevare incidono sul codice reale. (Dà l'esempio del Mars Climate Orbiter, che fallì tristemente a causa di un errore dimensionale in una costante importante).

  3. Nel codice reale, le unità vengono utilizzate raramente. Le persone non li usano, perché incorrere in calcoli di runtime o sovraccarico di memoria per creare tipi ricchi è troppo costoso e l'utilizzo di un codice di unità pre-esistente in C ++ è talmente notoriamente brutto che nessuno lo usa. (Empiricamente, nessuno lo usa, anche se le biblioteche sono in circolazione da un decennio).

  4. Pertanto, al fine di indurre gli ingegneri a utilizzare le unità in codice reale, avevamo bisogno di un dispositivo che (1) non incorre in sovraccarico di runtime e (2) sia notoriamente accettabile.

Il supporto della verifica della dimensione in fase di compilazione è l'unica giustificazione richiesta.

auto force = 2_N; 
auto dx = 2_m; 
auto energy = force * dx; 

assert(energy == 4_J); 

Vedi ad esempio PhysUnits-CT-Cpp11 , una piccola intestazione C ++ 11, C ++ 14 -solo libreria per analisi dimensionali in fase di compilazione e manipolazione e conversione di unità / quantità. Più semplice di Boost.Units , supporta simbolo unità valori letterali come m, g, s, metric prefissi come m, k, M, dipende solo dalla libreria C ++ standard, solo SI, potenze integrali delle dimensioni.

Hmm ... Non ho ancora pensato a questa funzione. Il tuo campione è stato ben pensato ed è sicuramente interessante. Il C ++ è molto potente come lo è ora, ma sfortunatamente la sintassi usata nelle parti di codice che leggi è a volte eccessivamente complessa. La leggibilità è, se non tutto, almeno molto. E tale funzionalità sarebbe orientata per una maggiore leggibilità. Se prendo il tuo ultimo esempio

assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

... Mi chiedo come lo esprimeresti oggi. Avresti una classe KG e una classe LB e confronteresti oggetti impliciti:

assert(KG(1.0f) == LB(2.2f));

E anche questo farebbe. Con tipi che hanno nomi più lunghi o tipi che non hai speranze di avere un costruttore così carino per sans che scrivono un adattatore, potrebbe essere una buona aggiunta per la creazione e l'inizializzazione implicita al volo. D'altra parte, puoi già creare e inizializzare oggetti usando anche metodi.

Ma sono d'accordo con Nils sulla matematica. Le funzioni di trigonometria C e C ++, ad esempio, richiedono input in radianti. Penso che in gradi, quindi una conversione implicita molto breve come quella pubblicata da Nils è molto bella.

Alla fine, sarà comunque zucchero sintattico, ma avrà un leggero effetto sulla leggibilità. E probabilmente sarà anche più facile scrivere alcune espressioni (sin (180.0deg) è più facile da scrivere rispetto a sin (deg (180.0)). E poi ci saranno persone che abusano del concetto. Ma poi, le persone che abusano del linguaggio dovrebbero usare linguaggi molto restrittivi piuttosto che qualcosa di espressivo come C ++.

Ah, il mio post non dice praticamente nulla tranne: andrà bene, l'impatto non sarà troppo grande. Non preoccuparti. : -)

Non ho mai avuto bisogno o desiderato questa funzione (ma potrebbe essere l'effetto Blub ) . La mia reazione istintiva è che è zoppo, e probabilmente si rivolge alle stesse persone che pensano che sia bello sovraccaricare l'operatore + per qualsiasi operazione che potrebbe essere considerata da remoto come aggiunta.

Il C ++ di solito è molto severo riguardo alla sintassi usata - a parte il preprocessore non c'è molto che puoi usare per definire una sintassi / grammatica personalizzata. Per esempio. possiamo sovraccaricare le operazioni esistenti, ma non possiamo definirne di nuove - IMO questo è molto in sintonia con lo spirito del C ++.

Non mi dispiace alcuni modi per un codice sorgente più personalizzato, ma il punto scelto mi sembra molto isolato, il che mi confonde di più.

Anche l'uso previsto può rendere molto più difficile la lettura del codice sorgente: una singola lettera può avere effetti collaterali di vasta portata che in nessun modo possono essere identificati dal contesto. Con la simmetria su u, l e f, la maggior parte degli sviluppatori sceglierà lettere singole.

Questo può anche trasformare l'ambito in un problema, l'uso di singole lettere nello spazio dei nomi globale sarà probabilmente considerato una cattiva pratica e gli strumenti che si suppone che mescolino più facilmente le librerie (spazi dei nomi e identificatori descrittivi) probabilmente ne vanificheranno lo scopo.

Vedo qualche merito in combinazione con " auto " ;, anche in combinazione con una libreria di unità come aumenta le unità , ma non abbastanza per meritare questa adizione.

Mi chiedo, tuttavia, quali idee intelligenti veniamo fuori.

Ho usato valori letterali utente per stringhe binarie come questa:

 "asd\0\0\0\1"_b

usando il costruttore std :: string (str, n) in modo che \ 0 non tagli la stringa a metà. (Il progetto lavora molto con vari formati di file.)

Questo è stato utile anche quando ho abbandonato std :: string in favore di un wrapper per std :: vector .

Il rumore di linea in quella cosa è enorme. Inoltre è orribile da leggere.

Fammi sapere, hanno ragionato questa nuova aggiunta di sintassi con qualche tipo di esempio? Ad esempio, hanno un paio di programmi che usano già C ++ 0x?

Per me, questa parte:

auto val = 3.14_i

Non giustifica questa parte:

std::complex<double> operator ""_i(long double d) // cooked form
{ 
    return std::complex(0, d);
}

Nemmeno se usassi la sintassi i anche in altre 1000 linee. Se scrivi, probabilmente scrivi anche oltre 10000 righe di qualcos'altro. Soprattutto quando probabilmente continuerai a scrivere per lo più ovunque questo:

std::complex<double> val = 3.14i

la parola chiave "auto" può essere giustificata, solo forse. Ma prendiamo solo C ++, perché è meglio di C ++ 0x in questo aspetto.

std::complex<double> val = std::complex(0, 3.14);

È come .. così semplice. Anche se tutte le parentesi rigide e appuntite sono solo zoppe se la usi ovunque. Non inizio a indovinare quale sintassi ci sia in C ++ 0x per trasformare std :: complex in complex.

complex = std::complex<double>;

Forse è qualcosa di semplice, ma non credo sia così semplice in C ++ 0x.

typedef std::complex<double> complex;

complex val = std::complex(0, 3.14);

Forse? > :)

Ad ogni modo, il punto è: scrivere 3.14i invece di std :: complex (0, 3.14); non ti fa risparmiare molto tempo in generale, tranne in alcuni casi super speciali.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top