Dlaczego std :: swap nie jest oznaczony constexpr przed C ++ 20?

14

W C ++ 20 std::swapstaje się constexprfunkcją.

Wiem, że standardowa biblioteka naprawdę pozostawała w tyle za językiem w oznaczaniu rzeczy constexpr, ale do 2017 r. <algorithm>Była prawie niezrozumiała, podobnie jak wiele innych rzeczy. A jednak - std::swapnie było. Niejasno pamiętam jakąś dziwną wadę językową, która uniemożliwia to oznakowanie, ale zapominam o szczegółach.

Czy ktoś może to wyjaśnić zwięźle i jasno?

Motywacja: musisz zrozumieć, dlaczego oznaczanie std::swap()podobnej funkcji constexprw kodzie C ++ 11 / C ++ 14 może być złym pomysłem .

einpoklum
źródło

Odpowiedzi:

11

Dziwny problem językowy to CWG 1581 :

Klauzula 15 [special] jest całkowicie jasne, że specjalne funkcje składowe są domyślnie zdefiniowane tylko wtedy, gdy są używane odr. Stwarza to problem dla stałych wyrażeń w nieocenionych kontekstach:

struct duration {
  constexpr duration() {}
  constexpr operator int() const { return 0; }
};

// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});

Problem polega na tym, że nie wolno nam niejawnie definiować constexpr duration::duration(duration&&)w tym programie, więc wyrażenie na liście inicjalizatora nie jest wyrażeniem stałym (ponieważ wywołuje funkcję constexpr, która nie została zdefiniowana), więc inicjator stężeń zawiera zawężającą się konwersję , więc program jest źle sformułowany.

Jeśli odkomentujemy wiersz nr 1, konstruktor ruchu jest domyślnie zdefiniowany, a program jest prawidłowy. Ta upiorna akcja na odległość jest wyjątkowo niefortunna. Implementacje różnią się w tym punkcie.

Możesz przeczytać resztę opisu problemu.

Rozwiązanie tego problemu zostało przyjęte w P0859 w Albuquerque w 2017 r. (Po wysłaniu C ++ 17). Problem ten blokował możliwość posiadania constexpr std::swap(rozwiązanego w P0879 ) i constexpr std::invoke(rozwiązanego w P1065 , który również zawiera przykłady CWG1581), zarówno dla C ++ 20.


Moim zdaniem najprostszym do zrozumienia przykładem jest kod z raportu o błędach LLVM wskazanego w P1065:

template<typename T>
int f(T x)
{
    return x.get();
}

template<typename T>
constexpr int g(T x)
{
    return x.get();
}

int main() {

  // O.K. The body of `f' is not required.
  decltype(f(0)) a;

  // Seems to instantiate the body of `g'
  // and results in an error.
  decltype(g(0)) b;

  return 0;
}

CWG1581 wszystko o kiedy constexpr funkcje składowe są zdefiniowane, a rozdzielczość, zapewnia, że są one jedynie zdefiniowane podczas eksploatacji. Po P0859 powyższe jest dobrze uformowane (typ bjest int).

Od std::swapistd::invoke oba mają polegać na sprawdzanie funkcji składowych (budowa MOVE / zadanie w dawnej i operator call / zastępcze połączeń w ostatni), oboje zależała od rozwiązania tego problemu.

Barry
źródło
Dlaczego więc CWG-1581 zapobiega / sprawia, że ​​niepożądane jest oznaczanie funkcji zamiany jako constexpr?
einpoklum
3
Wymiana @einpoklum wymaga std::is_move_constructible_v<T> && std::is_move_assignable_v<T>to true. Nie może się to zdarzyć, jeśli specjalne funkcje składowe nie są jeszcze generowane.
NathanOliver
@NathanOliver: Dodałem to do mojej odpowiedzi.
einpoklum
5

Powód

(z powodu @NathanOliver)

Aby zezwolić na constexprfunkcję zamiany, musisz sprawdzić - przed utworzeniem wystąpienia szablonu dla tej funkcji - czy typ zamiany jest możliwy do utworzenia i przypisania do ruchu. Niestety, z powodu wady języka rozwiązanej tylko w C ++ 20, nie możesz sprawdzić, ponieważ odpowiednie funkcje składowe mogły nie zostać jeszcze zdefiniowane, jeśli chodzi o kompilator.

Chronologia

  • 2016: Antony Polukhin przedstawia propozycję P0202 , aby zaznaczyć wszystkie<algorithm> funkcje jako constexpr.
  • Główna grupa robocza komitetu standardowego omawia wadę CWG-1581 . Ten problem sprawiał, że problematyczne było constexpr std::swap()równieżconstexpr std::invoke() - patrz wyjaśnienie powyżej.
  • 2017: Antony kilka razy zmienia swoją propozycję, aby ją wykluczyćstd::swap i kilka innych konstrukcji, co zostało zaakceptowane w C ++ 17.
  • 2017: Zgłoszono rezolucję dotyczącą wydania CWG-1581 jako P0859 i zaakceptowana przez komitet standardowy w 2017 r. (Ale po wysłaniu C ++ 17).
  • Koniec 2017: Antony przedstawi propozycję uzupełniającą, P0879 , aby uczynićstd::swap() constexpr po rezolucji CWG-1581.
  • 2018: propozycja uzupełniająca została przyjęta (?) Do C ++ 20. Jak wskazuje Barry, tak samo jest constexprstd::invoke() .

Twój konkretny przypadek

Możesz użyć constexprzamiany, jeśli nie sprawdzasz możliwości konstruowania ruchów i przypisywania ruchów, ale raczej bezpośrednio sprawdzasz inne cechy typów, które zapewniają to w szczególności. np. tylko prymitywne typy i brak klas lub struktur. Teoretycznie możesz zrezygnować z kontroli i po prostu poradzić sobie z napotkanymi błędami kompilacji oraz niestabilnym przełączaniem się między kompilatorami. W każdym razie nie zastępuj std::swap()tego rodzaju rzeczami.

einpoklum
źródło