Чому std :: swap не позначений constexpr перед C ++ 20?


14

В C ++ 20, std::swap стає constexprфункцією.

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

Чи може хтось пояснити це лаконічно і чітко?

Мотивація: потрібно зрозуміти, чому може бути поганою ідеєю позначити функцію std::swap()-подобний constexprкод у коді C ++ 11 / C ++ 14.

Відповіді:


11

Дивна мова - CWG 1581 :

Пункт 15 [спеціальний] цілком зрозуміло, що функції спеціальних членів визначаються лише неявно під час їх використання. Це створює проблему для постійних виразів у неоцінених контекстах:

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 ), так і для 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 зазначене вище добре сформоване (тип bєint ).

Оскільки std::swapі std::invokeобидва повинні покладатися на перевірку функцій членів (переміщення конструкції / призначення в першому та оператора викликів / сурогатних викликів в другому), вони обоє залежали від вирішення цього питання.


Отже, чому CWG-1581 запобігає / робить небажаним позначати функцію swap як constexpr?
einpoklum

3
@einpoklum своп вимагає std::is_move_constructible_v<T> && std::is_move_assignable_v<T>це true. Це не може статися, якщо функції спеціального члена ще не сформовані.
NathanOliver

@NathanOliver: додав це до моєї відповіді.
einpoklum

5

Причина

(завдяки @NathanOliver)

Щоб дозволити функцію constexprswap, перед тим, як ініціювати шаблон для цієї функції, ви повинні перевірити, що тип, що розміщується, може переміщуватися та конфігуруватися з переміщенням. На жаль, через мовний дефект, вирішений лише на 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заміну, якщо ви не перевіряєте можливість переміщення та конфігурацію переміщення, а навпаки, безпосередньо перевіряєте наявність інших типів, що забезпечує саме це. наприклад, лише примітивні типи і відсутні класи та структури. Або теоретично ви можете відмовитися від перевірок і просто вирішити будь-які помилки компіляції, з якими ви можете зіткнутися, і з нечіткою поведінкою перемикання між компіляторами. У будь-якому випадку не замінюйте std::swap()подібну річ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.