Co decyduje o tym, czy funkcja constexpr jest wyrażeniem stałym?

Nov 30 2020

(używany kompilator to gcc z c ++ 17, o ile wiem (trudno to znaleźć w Visual Studio))

#include <iostream>

using namespace std;

void increment( int& v )
{
    ++v;
}

int constexpr f()
{
    int v = 0;
    increment( v );
    return v;
}

int main( )
{
    cout << f( ) << '\n';
}

Powyższy kod daje błąd przy kompilacji:

Funkcja constexpr „f” nie może skutkować wyrażeniem stałym.

Jak rozumiem, incrementdzieje się tak dlatego, że funkcja nie jest constexpr. Co mnie wprawia w zakłopotanie, to fakt, że następujący kod kompiluje się dobrze:

#include <iostream>

using namespace std;

void increment( int& v )
{
    ++v;
}

int constexpr f()
{
    int v = 0;
    for( int i = 0; i < 1; ++i )
    {
        increment( v );
    }   
    return v;
}

int main( )
{
    cout << f( ) << '\n';
}

Ten kod jest funkcjonalnie taki sam i kompiluje się, mimo że inkrementacja nadal nie jest wartością constexpr. Nie rozumiem, jak to możliwe, że pętla for przez zakres [0, 1) powoduje, że kompilator zdaje sobie sprawę, że funkcja fjest w rzeczywistości constexpr.

Jeśli ktoś może dać wgląd w constexpr w języku c ++ i tę pozorną niespójność, byłbym bardzo wdzięczny.

Odpowiedzi

12 aschepler Nov 30 2020 at 22:23

Oba programy są „źle sformułowane, nie wymaga diagnostyki”, zgodnie z [dcl.constexpr] / 6 :

W przypadku funkcji constexpr lub konstruktora constexpr, który nie jest domyślny ani szablon, jeśli nie istnieją żadne wartości argumentów, takie że wywołanie funkcji lub konstruktora mogłoby być wartościowanym podwyrażeniem podstawowego wyrażenia stałego lub, w przypadku konstruktora, oszacowanym wyrażeniem podrzędnym inicjalizacja pełnego wyrażenia jakiegoś obiektu o stałej inicjalizacji ( [basic.start.static] ), program jest źle sformułowany, nie jest wymagana diagnostyka.

To trochę dziwne, że gcc po prostu nie zauważa problemu z drugim programem, ale nadal jest zgodny.

Zauważ, że diagnostyka byłaby wymagana, gdyby fzostała użyta w kontekście, który faktycznie wymaga na przykład stałego wyrażenia constexpr int n = f();.

Niektóre rzeczy nigdy nie są dozwolone w funkcji constexpr. Wymagają one diagnostyki (zazwyczaj komunikatu o błędzie), nawet jeśli funkcja nigdy nie jest używana w stałym wyrażeniu - zobacz odpowiedź cigien . Jednak programy, o których mowa, nie naruszają żadnych z tych bardziej rygorystycznych zasad.

6 cigien Nov 30 2020 at 22:27

Ponieważ nie wywołujesz fwyrażenia stałego, twoje pytanie brzmi, czy kompilator jest wymagany do zdiagnozowania tego, fczego nie można wywołać w wyrażeniu stałym, opierając się wyłącznie na jego definicji .

Wymagania dotyczące definicji z constexprfunkcji są wymienione tutaj :

Definicja funkcji constexpr spełnia następujące wymagania:

(3.1) jego zwracany typ (jeśli istnieje) będzie typem dosłownym;

(3.2) każdy z jego typów parametrów jest typem literalnym;

(3.3) nie będzie to coroutine;

(3.4) jeśli funkcja jest konstruktorem lub destruktorem, jej klasa nie powinna mieć żadnych wirtualnych klas bazowych;

(3.5) jego korpus funkcyjny nie powinien obejmować

(3.5.1) instrukcja goto,

(3.5.2) etykieta identyfikacyjna,

(3.5.3) definicja zmiennej typu nieliteralnego lub statycznego lub wątkowego czasu przechowywania.

Jak widać, definicja fnie narusza żadnego z wymagań na liście. Więc kompilator jest zgodny, jeśli zdecyduje się tego nie diagnozować.

Jak wskazano w odpowiedzi Ascheplera , constexprtakie funkcje fnie mogą być wywoływane w ciągłym wyrażeniu, ale nie są diagnozowane jako takie, są uważane za źle sformułowane - niewymagane diagnostycznie.

2 MarshallClow Nov 30 2020 at 22:22

W rzeczywistości nie „dzwonisz” fw czasie kompilacji.

jeśli twoja główna funkcja zawiera: static_assert(f() == 1, "f() returned 1");Podejrzewam, że wystąpiłby błąd „f () nie jest wyrażeniem stałym”.

Oto powiązane pytanie

Sneftel Nov 30 2020 at 22:12

Standard wymaga, aby constexprfunkcja była możliwa do oszacowania w czasie kompilacji dla pewnego zestawu parametrów, ale nie dla wszystkich. Nie wymaga od kompilatorów diagnozowania constexprfunkcji wykonującej pewne rzeczy, które w pewnych okolicznościach mogą nie być kompilowane, ani nawet tego, czy taka funkcja ma taki zestaw parametrów. Pozwala to uniknąć konieczności rozwiązywania problemu zatrzymania.