Почему std :: swap не помечен как constexpr до C ++ 20?

14

В C ++ 20 std::swapстановится constexprфункцией.

Я знаю, что стандартная библиотека действительно отстала от языка в маркировке вещей constexpr, но к 2017 году <algorithm>была в значительной степени консекстом, как и куча других вещей. Пока - std::swapнет. Я смутно помню, что был какой-то странный дефект языка, который мешал этой маркировке, но я забыл детали.

Может кто-нибудь объяснить это кратко и ясно?

Мотивация: Нужно понимать, почему может быть плохой идеей отмечать std::swap()подобную функцию constexprв коде C ++ 11 / C ++ 14.

einpoklum
источник

Ответы:

11

Странная языковая проблема - CWG 1581 :

Из пункта 15 [special] совершенно ясно, что специальные функции-члены определяются неявно только тогда, когда они используются в odr. Это создает проблему для константных выражений в неоцененных контекстах:

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

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

Проблема здесь в том, что нам не разрешено неявно определять constexpr duration::duration(duration&&)в этой программе, поэтому выражение в списке инициализаторов не является константным выражением (потому что оно вызывает функцию constexpr, которая не была определена), поэтому инициализированный скобками инициализатор содержит сужающее преобразование , так что программа плохо сформирована.

Если мы раскомментируем строку # 1, конструктор перемещения неявно определен, и программа действительна. Это жуткое действие на расстоянии крайне неудачно. Реализации расходятся по этому вопросу.

Вы можете прочитать остальную часть описания проблемы.

Решение по этому вопросу было принято в P0859 в Альбукерке в 2017 году (после поставки C ++ 17). Эта проблема была блокирующей для того, чтобы иметь возможность иметь constexpr std::swap(решен в P0879 ) и a constexpr std::invoke(решен в P1065 , который также имеет примеры CWG1581), оба для C ++ 20.


Самым простым для понимания примером здесь, на мой взгляд, является код из сообщения об ошибке LLVM, указанного в 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 все о когда constexpr функции - члены определены, и гарантирует разрешение , что они определены только при использовании. После P0859 вышеупомянутое хорошо сформировано (тип bis int).

Так как std::swapи std::invokeте, и другие должны полагаться на проверку функций-членов (переместить конструкцию / назначение в первом и вызовы оператора / суррогатные вызовы во втором), они оба зависели от решения этой проблемы.

Барри
источник
Итак, почему CWG-1581 предотвращает / делает нежелательным помечать функцию подкачки как constexpr?
einpoklum
3
@einpoklum swap требует std::is_move_constructible_v<T> && std::is_move_assignable_v<T>is true. Этого не может быть, если специальные функции-члены еще не созданы.
Натан Оливер
@NathanOliver: добавил это в мой ответ.
einpoklum
5

Причина

(из-за @NathanOliver)

Чтобы разрешить функцию constexprподкачки, вы должны проверить - до создания экземпляра шаблона для этой функции - что тип подкачки является конструируемым при перемещении и назначаемым при перемещении. К сожалению, из-за языкового дефекта, исправленного только в C ++ 20, вы не можете проверить это, поскольку соответствующие функции-члены еще не были определены, поскольку это касается компилятора.

Хронология

  • 2016: Антоний Полухин подает предложение P0202 , чтобы пометить все <algorithm>функции как constexpr.
  • Основная рабочая группа стандартного комитета обсуждает дефект CWG-1581 . Этот вопрос сделал проблематичным иметь, constexpr std::swap()а также constexpr std::invoke()- см. Объяснение выше.
  • 2017: Антоний несколько раз пересматривает свое предложение, чтобы исключить std::swapи некоторые другие конструкции, и это принято в C ++ 17.
  • 2017: Решение по проблеме CWG-1581 представлено в виде P0859 и принято стандартным комитетом в 2017 году (но после поставки C ++ 17).
  • Конец 2017 года: Антоний представляет дополнительное предложение, P0879 , сделать std::swap()constexpr после разрешения CWG-1581.
  • 2018: дополнительное предложение принято (?) В C ++ 20. Как отмечает Барри, std::invoke()исправление constexpr .

Ваш конкретный случай

Вы можете использовать constexprподкачку, если вы не проверяете возможность перемещения и присваивания перемещения, а вместо этого непосредственно проверяете некоторые другие особенности типов, которые гарантируют это, в частности. например, только примитивные типы и нет классов или структур. Или, теоретически, вы можете отказаться от проверок и просто иметь дело с любыми ошибками компиляции, с которыми вы можете столкнуться, и с нестабильным переключением поведения между компиляторами. В любом случае, не заменяйте std::swap()на подобные вещи.

einpoklum
источник