Czy różnica dwóch instancji constexpr wskaźników __func__ wciąż jest constexpr?

14

Czy to jest poprawny C ++?

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

GCC i MSVC uważają, że jest w porządku, Clang uważa, że ​​tak nie jest: Compiler Explorer .


Wszystkie kompilatory zgadzają się, że ten jest w porządku: Eksplorator kompilatorów .

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

Clangowi znów się to nie podoba, ale inni się z tym zgadzają : Eksplorator kompilatorów

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

Co tu jest Myślę, że arytmetyka niepowiązanych wskaźników jest nieokreślonym zachowaniem, ale __func__zwraca ten sam wskaźnik, prawda? Nie jestem pewien, więc pomyślałem, że mogę to przetestować. Jeśli dobrze pamiętam, std::equal_tomogę porównać niepowiązane wskaźniki bez niezdefiniowanego zachowania:

#include <functional>

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

Clang uważa, że eq(__func__, __func__)nie jest stałym wyrażeniem, chociaż std::equal_to::operator() jest constexpr . Inne kompilatory nie narzekają: Eksplorator kompilatorów


Clang też tego nie skompiluje. Skarży się, że __func__ == __func__nie jest stałym wyrażeniem: Eksplorator kompilatora

int main() {
    static_assert(__func__ == __func__);
}
Ayxan
źródło
Od Function_definition , __func__jest tak-jeśli static const char __func__[] = "function-name";i to jest akceptowane odpowiednik Demo ...
Jarod42
Co ciekawe, działa, jeśli zainicjujesz zmienną constexpr __func__i użyjesz jej w static_assert ...
florestan 28.12.19
@ Jarod42 Więc to jest błąd w Clang?
Ayxan
@florestan jak to ? Nie da się też skompilować z Clangiem. Moim drugim i trzecim przykładem w tym pytaniu jest sposób, w jaki wspomniałeś. Jeden kompiluje, drugi nie.
Ayxan,
1
Zobacz także CWG1962 , który może __func__całkowicie usunąć z oceny constexpr.
Davis Herring

Odpowiedzi:

13

__func__w C ++ jest identyfikatorem. W szczególności odnosi się do określonego obiektu. Od [dcl.fct.def.general] / 8 :

Zmienna predefiniowana lokalnie dla funkcji _­_­func_­_­jest zdefiniowana tak, jakby była definicją formularza

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

podano, gdzie nazwa-funkcji jest łańcuchem zdefiniowanym przez implementację. Nie jest określone, czy taka zmienna ma adres inny niż adres dowolnego innego obiektu w programie.

Jako zmienna predefiniowana lokalnie dla funkcji , ta definicja (jak gdyby) pojawia się na początku bloku funkcyjnego. Jako takie, wszelkie zastosowania __func__w tym bloku będą odnosić się do tej zmiennej.

Jeśli chodzi o część „dowolny inny obiekt”, zmienna definiuje obiekt. __func__nazywa obiekt zdefiniowany przez tę zmienną. Dlatego w ramach funkcji wszystkie zastosowania __func__nazwy nazywają tę samą zmienną. Nieokreślone jest to, czy zmienna ta jest obiektem odrębnym od innych obiektów.

Oznacza to, że jeśli korzystasz z funkcji o nazwie fooi użyłeś literału "foo"w innym miejscu problemu, nie jest zabronione, aby implementacja miała zmienną będącą __func__tym samym obiektem, który "foo"zwraca literał . Oznacza to, że standard nie wymaga, aby każda funkcja, w której się __func__pojawia, musi przechowywać dane oddzielnie od samego literału łańcucha.

Teraz zasada „jak gdyby” w C ++ pozwala implementacjom na odchylenie od tego, ale nie mogą tego zrobić w sposób, który byłby wykrywalny. Tak więc, chociaż sama zmienna może mieć, ale nie musi, odrębny adres od innych obiektów, zastosowania __func__tej samej funkcji muszą zachowywać się tak, jakby odnosiły się do tego samego obiektu.

Clang wydaje się nie wdrażać w __func__ten sposób. Wygląda na to, że implementuje to tak, jakby zwróciło literał ciąg znaków wartości nazwy funkcji. Dwa odrębne literały łańcuchowe nie muszą odnosić się do tego samego obiektu, więc odejmowanie do nich wskaźników to UB. Nieokreślone zachowanie w stałym kontekście ekspresji jest źle sformułowane.

Jedyne, co sprawia, że ​​waham się powiedzieć, że Clang jest w 100% błędny, to [temp.arg.nontype] / 2 :

W przypadku nietypowego parametru szablonu typu odniesienia lub typu wskaźnika wartość stałego wyrażenia nie może odnosić się do (lub w przypadku typu wskaźnika nie powinien to być adres):

...

  • predefiniowana _­_­func_­_zmienna.

Widzisz, wydaje się, że pozwala to na pewne fałszowanie przy implementacji. Oznacza to, że chociaż __func__technicznie może być stałym wyrażeniem, nie można go użyć w parametrze szablonu. Jest traktowany jak dosłowny ciąg znaków, mimo że technicznie jest zmienną.

Więc na pewnym poziomie powiedziałbym, że standard mówi z obu stron jego ust.

Nicol Bolas
źródło
Tak więc, ściśle mówiąc, __func__może być stałym wyrazem we wszystkich przypadkach mojego pytania, prawda? Więc kod powinien się skompilować.
Ayxan
A co z „Nie jest określone, czy taka zmienna ma adres inny niż adres jakiegokolwiek innego obiektu w programie”. część? Nieokreślone zachowanie oznacza brak determinizmu w zachowaniu abstrakcyjnej maszyny. Czy może to być problematyczne dla oceny constexpr? Co się stanie, jeśli za pierwszym razem __func__adres będzie taki sam jak adres innego obiektu, a za drugim razem __func__nie będzie? To prawda, że ​​nie oznacza to, że adres różni się między tymi dwoma instancjami, ale nadal jestem zdezorientowany!
Johannes Schaub - litb
@ JohannesSchaub-litb: „ A co z„ Nie jest określone, czy taka zmienna ma adres różny od adresu dowolnego innego obiektu w programie. ”Część? „ A co z tym? __func__nie jest makrem; jest to identyfikator, który nazywa określoną zmienną, a zatem konkretny obiekt. Dlatego każde użycie __func__tej samej funkcji powinno skutkować uzyskaniem wartości glvalue odnoszącej się do tego samego obiektu. Lub bardziej konkretnie, nie można go wdrożyć w taki sposób, aby tak nie było.
Nicol Bolas,
@Nicol, który odnosi się do tego samego obiektu. Ale ten obiekt może w jednej chwili mieć ten sam adres co inny obiekt. A w innym momencie nie. Nie twierdzę, że to problem, ale przypominam wszystkim o tej możliwości. I w końcu mogę się mylić, więc mówię to również w nadziei na poprawienie lub potwierdzenie.
Johannes Schaub - litb
@ JohannesSchaub-litb: „ Ale ten obiekt może w jednej chwili mieć ten sam adres co inny obiekt. ” Nie jest to dozwolone w modelu obiektowym C ++. Dwa obiekty, z których żaden nie jest zagnieżdżony w drugim, nie mogą znajdować się w tym samym czasie w tym samym magazynie. A przedmiotowy obiekt ma statyczny czas przechowywania, więc jeśli nie użyjesz go do umieszczenia new, nigdzie nie dojdzie, dopóki program się nie zakończy.
Nicol Bolas,