La differenza tra due istanze constexpr di puntatori __func__ è ancora constexpr?


14

Questo C ++ è valido?

int main() {
    constexpr auto sz = __func__ - __func__;
    return sz;
}

GCC e MSVC pensano che sia OK, Clang pensa che non lo sia: Compiler Explorer .


Tutti i compilatori concordano sul fatto che questo è OK: Compiler Explorer .

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = p;
    constexpr auto sz = p2 - p;
    return sz;
}

Di nuovo Clang non piace questo, ma gli altri sono d'accordo: Compiler Explorer

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = __func__;
    constexpr auto sz = p2 - p;
    return sz;
}

Cosa c'è quassù? Penso che l'aritmetica su puntatori non correlati sia un comportamento indefinito ma __func__restituisce lo stesso puntatore, no? Non ne sono sicuro, quindi ho pensato di provarlo. Se ricordo bene, è std::equal_topossibile confrontare i puntatori non correlati senza un comportamento indefinito:

#include <functional>

int main() {
    constexpr std::equal_to<const char*> eq{};
    static_assert(eq(__func__, __func__));
}

Clang pensa che eq(__func__, __func__)non sia un'espressione costante, anche se std::equal_to::operator() è constexpr . Gli altri compilatori non si lamentano: Compiler Explorer


Anche Clang non compilerà questo. Si lamenta che __func__ == __func__non sia un'espressione costante: Compiler Explorer

int main() {
    static_assert(__func__ == __func__);
}

Da Function_definition , __func__è come-se static const char __func__[] = "function-name";e
quell'equivalente

È interessante notare che funziona se si inizializza una variabile constexpr __func__e la si utilizza in static_assert ...
florestan,

@ Jarod42 Quindi questo è un bug in Clang?
Ayxan,

@florestan piace questo ? Non si compila neanche con Clang. I miei esempi 2 e 3 nella domanda sono il modo in cui hai menzionato. Uno compila, l'altro no.
Ayxan,

1
Vedi anche CWG1962 , che potrebbe essere rimosso __func__completamente dalla valutazione di constexpr.
Davis Herring,

Risposte:


13

__func__in C ++ è un identificatore. In particolare, fa riferimento a un oggetto specifico. Da [dcl.fct.def.general] / 8 :

La variabile predefinita locale-funzione _­_­func_­_­ viene definita come una definizione del modulo

static const char __func__[] = "function-name";

era stato fornito, dove nome-funzione è una stringa definita dall'implementazione. Non è specificato se tale variabile abbia un indirizzo distinto da quello di qualsiasi altro oggetto nel programma.

Come variabile predefinita locale-funzione , questa definizione (come se) appare all'inizio del blocco funzione. Pertanto, qualsiasi utilizzo __func__all'interno di quel blocco farà riferimento a quella variabile.

Per quanto riguarda la parte "qualsiasi altro oggetto", una variabile definisce un oggetto. __func__nomina l'oggetto definito da quella variabile. Pertanto, all'interno di una funzione, tutti gli usi del __func__nome sono la stessa variabile. Ciò che non è definito è se quella variabile è un oggetto distinto da altri oggetti.

Vale a dire, se si utilizza una funzione denominata fooe si è utilizzato letteralmente "foo"altrove nel problema, non è vietato per un'implementazione avere la variabile__func__ come lo stesso oggetto "foo"restituito dal valore letterale . Cioè, lo standard non richiede che tutte le funzioni in cui__func__ appare deve archiviare i dati separatamente dalla stringa stessa.

Ora, la regola "come se" di C ++ consente alle implementazioni di deviare da questa, ma non possono farlo in un modo che sarebbe rilevabile. Quindi, mentre la variabile stessa può avere o meno un indirizzo distinto da altri oggetti, usa di__func__ stessa funzione devono comportarsi come se si riferissero allo stesso oggetto.

Clang non sembra implementare in __func__questo modo. Sembra implementarlo come se restituisse una stringa di valore letterale del nome della funzione. Due letterali stringa distinti non devono fare riferimento allo stesso oggetto, quindi sottrarre puntatori a loro è UB. E il comportamento indefinito in un contesto di espressione costante è mal formato.

L'unica cosa che mi rende titubante nel dire che Clang ha torto al 100% qui è [temp.arg.nontype] / 2 :

Per un parametro modello non tipo di riferimento o tipo di puntatore, il valore dell'espressione costante non deve fare riferimento a (o per un tipo di puntatore, non deve essere l'indirizzo di):

...

  • una _­_­func_­_variabile predefinita .

Vedi, questo sembra consentire qualche confusione con l'implementazione. Cioè, mentre__func__ tecnicamente può essere un'espressione costante, non puoi usarla in un parametro template. È trattato come una stringa letterale, anche se tecnicamente è una variabile.

Quindi, ad un certo livello, direi che lo standard parla da entrambi i lati della sua bocca.


Quindi, a rigor di termini, __func__può essere un'espressione costante in tutti i casi della mia domanda, giusto? Quindi il codice avrebbe dovuto essere compilato.
Ayxan,

Che dire "Non è specificato se una tale variabile abbia un indirizzo distinto da quello di qualsiasi altro oggetto nel programma." parte? Comportamento non specificato significa non determinismo nel comportamento della macchina astratta. Può essere problematico per la valutazione di constexpr? E se per la prima occorrenza __func__l'indirizzo fosse uguale a quella di un altro oggetto, e nella seconda occorrenza __func__non lo fosse? Certo, ciò non significa che l'indirizzo differisca tra le due istanze, ma sono ancora confuso!
Johannes Schaub - litb

@ JohannesSchaub-litb: " Che dire del" Non è specificato se una tale variabile abbia un indirizzo distinto da quello di qualsiasi altro oggetto nel programma. "Parte? " Che ne pensi? __func__non è una macro; è un identificatore che nomina una specifica variabile e quindi un oggetto specifico. Pertanto, qualsiasi utilizzo di __func__nella stessa funzione dovrebbe tradursi in un valore che si riferisce allo stesso oggetto. O più al punto, non può essere implementato in modo tale che non sarebbe il caso.
Nicol Bolas,

@Nicol che fa riferimento allo stesso oggetto. Ma quell'oggetto può avere in un istante lo stesso indirizzo di un altro oggetto. E nell'altro istante no. Non sto dicendo che è un problema, ma sto solo ricordando a tutti questa possibilità. E poi, dopo tutto, potrei anche sbagliarmi, quindi lo dico anche nella speranza di essere corretto o confermato.
Johannes Schaub - litb

@ JohannesSchaub-litb: " Ma quell'oggetto può avere in un istante lo stesso indirizzo di un altro oggetto. " Ciò non è consentito dal modello a oggetti C ++. Due oggetti, nessuno dei due nidificato all'interno dell'altro, non possono essere contemporaneamente nello stesso archivio nella loro vita. E l'oggetto in questione ha una durata di archiviazione statica, quindi se non si utilizza il posizionamento newsu di esso, non andrà da nessuna parte fino al termine del programma.
Nicol Bolas,
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.