Interested Article - C++17

C++17 (также известный как C++1z) — это название версии стандарта C++ ISO /IEC. Спецификации для C++17 были опубликованы в декабре 2017 года .

Значение константы __cplusplus стало 201703L , это используется для условной компиляции .

Удалены или запрещены

Удалены триграфы

Триграфы использовались для машин с нестандартной кодировкой и/или ограниченной клавиатурой: так, в немецком ASCII вместо скобок [\]{|}~ умляуты ÄÖÜäöüß . Ещё в конце 80-х, с распространением 8-битных кодировок, дешёвых резиномембранных клавиатур и качественно локализованных компьютеров, триграфы фактически потеряли смысл, и тридцать лет спустя были закономерно исключены .

// Will the next line be executed????????????????/
a++;      /* с триграфами эта строка закомментирована — триграф ??/ эквивалентен \ */

Удалено ключевое слово register

Язык Си был «переносимым ассемблером»: он позволял делать быстрые программы, компилирующиеся на разных компьютерах, к тому же использовал ассемблерные утилиты ( компоновщик , библиотекарь). Понятия вроде « заголовочный файл » и « единица трансляции » — отголоски тех времён.

Слово register изначально связано с ручной оптимизацией программы. Современные компиляторы «под капотом» делают огромное количество оптимизаций, и подобное ручное управление представляется излишним. Ещё в Си++11 слово объявили нежелательным. Слово всё ещё остаётся зарезервированным, и его могут когда-нибудь задействовать с другой целью — как в Си++11 auto .

Удалена операция ++ для bool

Операция явно небезопасна и запрещена ещё в Си++98 . Операция -- отсутствует и так.

Удалены заявленные исключения

Заявленные исключения void f() throw(A, B, C); , имеющиеся, например, в Java , приносят больше вреда, чем пользы. Запрещены в Си++11, удалены в Си++17. Остался throw() как синоним для noexcept(true) .

Удалены типы и функции, получившие замену (и ставшие запрещёнными) в Си++11

В их числе std::auto_ptr , std::random_shuffle и старые функциональные адаптеры .

Вместо них используются unique_ptr , shuffle и новые функциональные шаблоны, основанные на function / bind . Заявляется, что любой код на auto_ptr может быть механически преобразован в unique_ptr , с простым добавлением std::move там, где идёт передача владения.

Также удалены отдельные части iostream , запрещённые ещё в Си++98 .

Удалены конструкторы для std::function, принимавшие аллокатор

Всего пять перегрузок, включая эту

template< class Alloc >
function( std::allocator_arg_t, const Alloc& alloc ) noexcept;

Из-за непонятной семантики и сложностей реализации их удалили без предварительного запрета .

Запрещены крайне редкие возможности стандартной библиотеки

Запрещено несколько редких возможностей стандартной библиотеки:

  • allocator<void> — оказался невостребованным;
  • часть функций allocator — дублируется шаблоном allocator_traits ;
  • raw_storage_iterator — не вызывает конструкторов и потому ограничен по применению;
  • get_temporary_buffer — имеет неочевидные подводные камни;
  • is_literal_type — бесполезен для обобщённого кода, но оставлен, пока в Си++ существует понятие «литеральный тип»;
  • iterator — проще писать итераторы с нуля, чем основываться на нём;
  • codecvt — на поверку работал очень плохо, комитет призвал пользоваться специализированными библиотеками;
  • shared_ptr::unique() — из-за ненадёжности в многопоточной среде.

Полностью удалить обещают в Си++20.

Запреты, связанные с новыми функциями Си++17

  • result_of invoke_result — более простой синтаксис, основанный на выведении типов Си++11 ;
  • bool uncaught_exception() int uncaught_exceptions() — в обработке одного исключения система может выбросить другое, так что могут «висеть» необработанными и несколько исключений. Проверить, сколько их было в конструкторе и сколько стало в деструкторе — более надёжный и «бесплатный» с точки зрения имеющихся библиотек метод определения, выбрасывать исключение из деструктора или нельзя .

Удалены заголовки библиотеки Си

С переходом на Си11 удалены заголовочные файлы <ccomplex> , <cstdalign> , <cstdbool> , <ctgmath> . Файл <ciso646> не запрещён .

auto x{}; больше не создаёт initializer_list

Добавленный в Си++11 универсальный инициализатор int x{}; позволяет одним синтаксисом создать объект, структуру, массив. В Си++17 уточнено: если вместо типа стоит auto — пользователь хочет создать один объект и никаких initializer_list не нужно.

При этом auto x = {1, 2, 3}; продолжает создавать: с одной стороны, для совместимости с for (auto x : {1, 2, 3}) , с другой — для одного объекта есть auto x = 1; .

auto x1 = { 3 };   // std::initializer_list<int>
auto x2 { 1, 2 };  // теперь ошибка
auto x3 { 3 };     // int

Глобальные изменения

Спецификация исключений — теперь часть системы типов

Функции void f () noexcept ( true ); и void f () noexcept ( false ); — теперь функции с разными типами (но не могут составлять перегруженный набор). Это позволит API требовать callback ’и, которые не выбрасывают исключений, а также оптимизировать код под отсутствие таковых .

New с чрезмерным выравниванием

В Си++11 появилась возможность создавать структуры данных, чьё выравнивание больше, чем теоретическое. Эта возможность была подхвачена операцией new .

class alignas(16) float4 {
	float f[4];
};
float4 *p = new float4[1000];

Появилась перегрузка операции new с дополнительным параметром, чтобы корректно разместить в памяти чрезмерно выравненный объект.

Обязательное избавление от копирования

Изменён смысл понятия prvalue: теперь это всего лишь инициализация.

В коде SomeType a = 10; хоть всё ещё требуется и конструктор, и операция =, гарантированно будет вызван только конструктор.

Это значит, что функции могут возвращать типы, которые нельзя копировать и перемещать.

Более строгий порядок вычисления

Теперь операции a.b , a->b , a->*b , a(b1, b2, b3) , b += a (и аналоги для других операций), a[b] , a << b и a >> b вычисляются в порядке a → b, чтобы держать под контролем побочные эффекты .

Если их вызвать как функции (например, operator += (a, b) ), порядок остаётся неопределённым.

Расширили понятие «константа в шаблоне»

Существуют шаблоны, принимающие константу.

template <int N> struct Array
{
  int a[N];
};

Что может быть константой N, и что не может — объявлено от противного. Константа в шаблоне не может быть указателем на поле, на временный объект, на строковый литерал, на результат typeid и на стандартную переменную __func__ ;

В for могут быть begin и end разного типа

Теперь for (auto v : x) означает auto __begin = begin-expr; auto __end = end-expr; , допуская begin и end разных типов.

Это — база для прохода по диапазонам (ranges), работа над которыми продолжается .

Редакционные правки

Понятие «непрерывный итератор»

Массивы std::vector и std::string имеют дело с непрерывными участками памяти. Для них ввели понятие «непрерывный итератор» . Концептуально ничего не изменилось.

Дали определения и другим понятиям — forwarding reference , default member initializer , templated entity . Это работа над концепциями Си++20.

Запрещены символы u'x' и U'x', не кодируемые одним символом

Ранее подобное поведение определялось реализацией.

Заодно сделали «символы UTF-8», которые имеют тип char и могут держать коды от 0 до 127, по аналогии со строками UTF-8 — по видимому, чтобы программа меньше зависела от настроек локали на компьютере .

Временно запрещён memory_order_consume

Из-за неадекватной семантики метод упорядочивания «consume» устно (без отметки [[ deprecated ]] ) запретили, призвав пользоваться методом «acquire». Работа над новой семантикой всё ещё ведётся и, возможно, запрет когда-нибудь снимут .

В любом случае на PowerPC и ARM все загрузки автоматически будут consume , но не все — acquire , и метод consume может сберечь такты в кроссплатформенном коде .

Язык

static_assert с одним аргументом

Если static_assert не сработал, не всегда требуется сообщать программисту, что не так — часто он и сам может понять из констекста. .

static_assert(sizeof(wchar_t) == 2);

inline для глобальных переменных и констант

Теперь можно в заголовочном файле написать inline const ClassName INSTANCE_NAME и все cpp-файлы будут ссылаться на один объект — в отличие от const ClassName INSTANCE_NAME или static const ClassName INSTANCE_NAME .

Новые стандартные аннотации

  • [[fallthrough]] : в одном из разделов оператора switch мы намеренно «проваливаемся» в следующий. Возможная реализация устройства Даффа
int n = (count + 7) / 8;
if (!count) return;
switch (count % 8) {
case 0: do { *to = *from++; [[fallthrough]];
case 7:      *to = *from++; [[fallthrough]];
case 6:      *to = *from++; [[fallthrough]];
case 5:      *to = *from++; [[fallthrough]];
case 4:      *to = *from++; [[fallthrough]];
case 3:      *to = *from++; [[fallthrough]];
case 2:      *to = *from++; [[fallthrough]];
case 1:      *to = *from++;
        } while (--n > 0);
}
  • [[nodiscard]] : вызов функции как процедуры считается ошибкой — например, это «чистая» функция вроде string::empty() , вся работа которой заключается в возврате значения, или протокол работы с объектом требует что-то сделать с возвращённым значением, как в unique_ptr::release() . В более позднем стандарте C++20 появилась возможность указать причину, почему вызов ошибочен.
class SmartPtr {  // собственная реализация unique_ptr
public:
   /// Передаёт управляемый объект под ручное управление
   /// @return  указатель на управляемый объект
   [[nodiscard]] Payload* release();
};

SmartPtr p;

Payload* data = p.release();   // правильное использование умного указателя
  delete data;
p.release();   // warning: ignoring return value of 'SmartPtr::release()', declared with attribute nodiscard
(void)p.release();  // так глушат предупреждение
  • [[maybe_unused]] : в каком-то из режимов компиляции ( Windows / POSIX , отладка/выпуск) тот или иной элемент не используется, и это не ошибка.
// Для краткости сделаем простейшую архитектуру, позволяющую UTF-32 → 16
//   и копирование UTF-16 как есть, и больше ничего
// QString всегда UTF-16, а wstring зависит от ОС
template <int Sz> void append(QString& s, unsigned long ch);

// версия для Windows, wstring = UTF-16
template<> [[maybe_unused]] inline void append<2>(QString& s, unsigned long ch)
   { s.append(static_cast<uint16_t>(ch); }

// версия для POSIX, wstring = UTF-32
template<> [[maybe_unused]] void append<4>(QString& s, unsigned long ch)
   {}  // кодировка кодовой позиции в UTF-16, для краткости опустим

std::wstring s = L"\U0001F60E";    // смайлик в очках
QString r;
// Для краткости мы делаем точную копию и столь сложный код не нужен.
// Но бывает нужен в какой-нибудь обработке — например, разэкранировании символов.
for (auto c : s)
  append<sizeof(c)>(r, c);
Или параметр намеренно не используется, но имя оставлено для документирования.
class ISoccerSeason {  // интерфейс
public:
   /// @pre      обе команды участвуют в этом сезоне.
   /// @return   true, если будет сыгран матч между командой home на своём поле и away в гостях
   /// @warning  В типичном футбольном сезоне обе команды сыграют и на своём, и на чужом поле.
   virtual bool doTeamsPlay([[maybe_unused]] const Team& home, [[maybe_unused]] const Team& away) const
      { return true; }
   virtual ~ISoccerSeason() = default;
};

Использование typename во вложенных шаблонах

Недоработка языка Си++: в шаблонах typename и class кое-где не взаимозаменяемые .

template<template<typename> class X> struct C;    // Остаётся OK
template<template<typename> typename X> struct D; // Теперь OK

Оба ключевых слова явно объявлены взаимозаменяемыми.

Структурное связывание

Появился новый способ объявления переменных для распаковки сложных объектов, который получил название структурного связывания .

auto [place,wasInserted] = someMap.emplace(key, value);

Работает для пар, кортежей и прочих типов, где работает std :: get .

Запись namespace A::B

Определение вложенных пространств имён: namespace A::B {} как сокращение для namespace A { namespace B {} } ;

Аннотации для пространств имён и элементов перечисляемого типа

Например:

enum class TriBool {
  NO,
  MAYBE,
  YES,
  NN [[maybe_unused]],
  UNSPECIFIED [[deprecated("Переименован в MAYBE")]] = MAYBE
};
constexpr int TriBool_N = static_cast<int>(TriBool::NN);
const char* triBoolNames[TriBool_N] = { "no", "maybe", "yes" };

Какой-то заявленной цели пока нет , но это позволит разработчикам компиляторов придумать таковую — например, объявить, что элемент NN особый и его не надо присваивать переменным, обрабатывать в switch .

If при компиляции

Концепция SFINAE позволила сделать несложный шаблон enable_if , который обеспечивает разную функциональность для разных типов, но даёт тяжеловесный код. В Си++17 можно упростить программу: оператор if constexpr(expression) инстанцирует код, если выражение в скобках истинно .

template <class T>
constexpr T absolute(T arg) {
  return arg < 0 ? -arg : arg;
}

template <class T>
constexpr auto precision_threshold = T(0.000001);

template <class T>
constexpr bool close_enough(T a, T b) {
  if constexpr (is_floating_point_v<T>) // << !!
    return absolute(a - b) < precision_threshold<T>;
  else
    return a == b;
}

В данном случае мы убеждаемся, что разница между дробными числами невелика, а целые просто проверяем на равенство.

Упрощённый синтаксис двухместной операции в переменных шаблонах

Упакованные выражения :

template<typename... As> bool foo(As... args)
    { return (args && ...); }

Шестнадцатеричное представление дробных чисел

Шестнадцатеричная мантисса и десятичный порядок: 0xC.68p+2, 0x1.P-126 , аналогично подстановке %a . Си поддерживает этот синтаксис с версии 99 .

Инициализация локальной переменной в if/switch

Аналогично инициализации локальных переменных в for , делает код компактнее .

if (auto it = m.find(key); it != m.end()) 
    return it->second;

Using в атрибутах

// Было
void f() {
    [[rpr::kernel, rpr::target(cpu,gpu)]] // повтор
    do_task();
}

// Стало
void f() {
    [[using rpr: kernel, target(cpu,gpu)]]
    do_task();
}

Бестиповые параметры в шаблонах

Позволяют задавать шаблонные параметры любого типа через auto .

template<auto X> struct B { static constexpr auto value = X; };
B<5> b1;   // OK: template parameter type is int
B<'a'> b2; // OK: template parameter type is char
B<2.5> b3; // error: template parameter type cannot be double

Захват лямбда-объектом *this

Было: [ self = * this ]{ self . f (); } . Стало: [ * this ]{ f (); } .

Можно инициализировать enum class числом

enum class иногда применяется, чтобы сделать другой целый тип, не совместимый ни с чем. Теперь переменные этого типа можно инициализировать числами

enum class Handle : intptr_t { INVALID = 0 };
Handle h { 42 };
Handle h = 42;     // запрещено

Библиотека

Мелкие доработки библиотеки

  • Неконстантная перегрузка string::data . Используется для вызова низкоуровневых строковых функций, которые принимают участок памяти определённой длины и заполняют его символами (например, WinAPI ). До Си++11 использовался const_cast <char*>(x.data()) , до Си++17 — &x.front() .
  • emplace_back одного элемента возвращает ссылку. Позволяет написать такую конструкцию:
v.emplace_back("alpha", "bravo").doSomething();
  • Стандартную библиотеку Си обновили с C99 до C11 .
  • Функции std::size(x) , std::begin(x) , std::end(x) , std::empty(x) . Позволяют писать общий шаблонный код для контейнеров STL и массивов . К тому же std::size — нужная функция, которую ранее часто писали своими силами с ошибками.
  • Добавлена частичная специализация bool_constant < bool B > = integral_constant < bool , B > ;
  • Добавились функции-свойства для SFINAE : is_swappable , is_nothrow_swappable , is_swappable_with , is_nothrow_swappable_with , is_aggregate (составной тип), has_unique_object_representations (тривиально копируемый объект, и любые два объекта с одинаковым значением имеют одинаковое внутреннее представление).
  • Расширена библиотека работы с неинициализированной памятью. Появились функции uninitialized_default_construct , uninitialized_value_construct , uninitialized_move , destroy , destroy_at , а также их версии для n элементов.
  • Новый шаблон void_t < T > = void . Упрощает создание SFINAE -шаблонов, которые можно раскрыть, если тип T существует .
  • Для std :: search добавилась версия с объектом-искателем. По умолчанию существуют три искателя: простейший, Бойер-Мур и Бойер-Мур-Хорспул .
  • Новая функция make_from_tuple инициализирует тип T данными из кортежа.
  • Новая константа atomic :: is_always_lock_free определяет, является ли атомарная переменная неблокирующей .
  • В chrono добавили функции округления вверх, вниз и до ближайшего.
  • В map / set добавили функции переброски ( merge ) и извлечения ( extract ) элементов.
  • Добавился тип shared_ptr < T >:: weak_type = weak_ptr < T > .
  • В некоторых случаях аллокаторы могут иметь неполный тип. Теперь возможны рекурсивные структуры наподобие struct X { std :: vector < X > data ; }; . Крупные компиляторы давно поддерживают такое, осталось только заспецифицировать.
  • Добавились неявные конструкторы в pair и tuple .
  • unique_ptr / shared_ptr могут работать с Си-массивами ( shared_ptr < string [] > ( new string [ n ]) ). В Си++14 требовалось протаскивать правильную функцию удаления ( shared_ptr < string [] > ( new string [ n ], default_delete < string [] > () ) ).
  • Уточнена работа common_type .

Новый тип

Часто бывает нужно передать неизменную строку в другой участок кода, это можно сделать такими методами:

void doSmth(const char *s);         // а что, если в строке нулевой символ? Да и внутренности функции становятся ошибкоопасными
void doSmth(const std::string &s);  // а что, если строка — не string, и придётся выделять память?

В C++17 появился тип string_view — строка, имеющая только указатель и длину, без владения, управления памятью и даже без завершающего нуля — и поэтому она не имеет функции c_str() . Изменять можно только границы (начало/длину), но не символы. Задача программиста — сделать, чтобы объект не пережил тот буфер памяти, где хранится строка, и передача параметров — отличное применение для него. Объект string_view очень маленький (2·битность машины), и его стоит передавать по значению, а не по ссылке.

string_view сам по себе является абстракцией — он абстрагируется от метода хранения строки, требуя только одно — чтобы текстовые данные были последовательными байтами в памяти. Только сложные необычные структуры (например, строп/канат ) хранят строки вразброс. А все остальные — и string , и const char * , и разного рода массивы — преобразуются в string_view .

Размер строки кэша

Есть две новые константы, hardware_constructive_interference_size и hardware_destructive_interference_size . Таким образом пользователь может избежать ложного общего доступа (destructive interference) и улучшить локальность (constructive interference).

struct keep_apart {
  alignas(hardware_destructive_interference_size) atomic<int> cat;
  alignas(hardware_destructive_interference_size) atomic<int> dog;
  // cat далеко от dog, их можно менять из разных потоков.
};

struct together {
  atomic<int> dog;
  int puppy;
};
struct kennel {
  //...
  alignas(sizeof(together)) together pack;
  //...
};
static_assert(sizeof(together) <= hardware_constructive_interference_size);
// убеждаемся, что together занимает одну строку кэша.

Теоретически обе константы должны быть одинаковыми, но для поддержки неоднородных архитектур решено было сделать две константы.

Новый тип shared_mutex

Мьютекс, позволяющий читать параллельно и писать одному . Блокировщики для него называются shared_lock и unique_lock .

Автоматическое определение типа параметра контейнера

В библиотеке появились функции, так называемые deduction guides , позволяющие делать такое:

std::pair p(2, 4.5);   // 1

std::vector<int> v = {1, 2, 3, 4};
std::vector x(v.begin(), v.end());   // 2

Новые функции вставки в ассоциативный массив с неповторяющимся ключом

Для и добавились две новых функции .

#include <iostream>
#include <map>

class Pair {
public:
    int value1, value2;
    Pair() : value1(0), value2(0) {}
    explicit Pair(int aValue1) : value1(aValue1), value2(0) {}
    Pair(int aValue1, int aValue2)
        : value1(aValue1), value2(aValue2) {}
};

int main()
{
    std::map<std::string, Pair> m;

    // C++11
    m["a"] = Pair(3, 4);
    m.emplace("a", 1);       // Pair создаётся всегда

    // C++17
    m.insert_or_assign("a", Pair(3, 4));
    m.try_emplace("a", 1);   // Pair создаётся когда надо

    return 0;
}

Новые математические функции

Внесены в пространство имён std нестандартные математические функции: beta , cyl_ bessel _ i/j/k , cyl_ neumann , [comp_] ellint_1/2/3 , expint , hermite , [assoc_] laguerre , [assoc_] legendre , riemann_zeta , sph_bessel , sph_legendre , sph_neumann . За пределами std (в math.h ) их нет.

Из первого предложения (2010): «Мы надеемся, что принятие этого предложения даст посыл разным сообществам вычислителей, что, несмотря на расхожее поверье, Си++ тоже вполне годится для их отрасли». Тогда его не приняли. Сейчас основные производители библиотек ( Dinkumware , Boost , GCC ) уже имеют эти функции.

Также добавились вычисление НОД и НОК , функция приведения в диапазон ( clamp ) , трёхмерная гипотенуза hypot ( x , y , z ) .

Библиотека файловой системы

Библиотека файловой системы, основанная на boost::filesystem , позволяет:

  • автоматическую интернационализацию имён файлов в зависимости от особенностей ОС. Библиотека скрывает, в какой кодировке она работает, и сама конвертирует имена в нужную — как минимум в определённую локалью однобайтовую и различные варианты Юникода;
  • проход по каталогам (в том числе рекурсивный);
  • определение типов файлов (обычный, каталог , сокет …);
  • деление пути к файлу на составные части: диск, каталог, имя и расширение;
  • создание каталогов, копирование файлов, удаление каталогов и файлов (в том числе рекурсивное);
  • получение имён для временных файлов .

Вариативные типы

Появился класс std :: any , способный содержать данные любого типа . От реализаций требуется, чтобы небольшие объекты помещались в any без выделения памяти. Функция any_cast требует точного совпадения типа, и any_cast < double > ничего не даст, если внутри объекта int .

std::cout << std::boolalpha;
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << std::endl;
a = 3.14;
std::cout << a.type().name() << ": " << std::any_cast<double>(a) << std::endl;
a = true;
std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << std::endl;

// i: 1
// d: 3.14
// b: true

Также есть более простые std::variant<int, bool, double> и std::optional<T> .

Низкоуровневые функции преобразования число-текст

Известный недостаток Си++: для низкоуровневого преобразования чисел в текст без выделения памяти приходится запускать тяжёлую и ненадёжную sprintf , а встроенное преобразование текста в число, оставшееся с Си, довольно ненадёжно.

Теперь есть встроенные локаленезависимые сверхскоростные from_chars и to_chars . Устроены они так, что не требуют (и не производят) закрывающего нуля и могут работать, например, на string_view . Из-за ограниченности и локаленезависимости предназначены они в первую очередь для JSON и XML , где нужна огромная скорость.

Новый тип polymorphic_allocator

Структуры данных STL ( строки , вектора и прочее) содержат шаблонный параметр — аллокатор памяти. Этот аллокатор работает как концепция обобщённого программирования, а не как интерфейс объектно-ориентированного: выделение памяти в куче и пуле даёт разные несовместимые типы. Класс polymorphic_allocator — стандартное начало для редкой задачи: в зависимости от каких-то условий, выделять память то в куче, то в пуле.

Сам по себе polymorphic_allocator — не интерфейс, но он связан с интерфейсом memory_resource .

Новый шаблон std :: invoke

Позволяет единообразно вызывать функции, объекты с операцией () ( функторы ) и лямбда-объекты . Также добавились функции is_invocable , is_invocable_r , invoke_result .

Параллельные версии алгоритмов STL

Для 69 алгоритмов из < algorithm > , < numeric > и < memory > придуманы параллельные версии .

См. также

Ссылки

  • Черновик стандарта, , от 21.03.2017

Примечания

  1. . Дата обращения: 4 декабря 2017. 17 мая 2013 года.
  2. . Дата обращения: 28 марта 2016. 8 сентября 2020 года.
  3. (6 мая 2014). Дата обращения: 28 марта 2016. 9 июля 2018 года.
  4. от 11 сентября 2018 на Wayback Machine , IBM paper N4210, 2014-10-10.
  5. . Дата обращения: 20 августа 2018. 14 сентября 2017 года.
  6. . Дата обращения: 20 августа 2018. 11 сентября 2017 года.
  7. . Дата обращения: 20 августа 2018. 13 сентября 2017 года.
  8. . Дата обращения: 28 марта 2016. 20 октября 2017 года.
  9. . Дата обращения: 28 марта 2016. 19 марта 2015 года.
  10. . Дата обращения: 20 августа 2018. 22 августа 2017 года.
  11. . Дата обращения: 20 августа 2018. 17 сентября 2017 года.
  12. . Дата обращения: 20 августа 2018. 13 сентября 2017 года.
  13. . Дата обращения: 20 августа 2018. 16 сентября 2017 года.
  14. . Дата обращения: 20 августа 2018. 7 июля 2017 года.
  15. . Дата обращения: 20 августа 2018. 5 июля 2017 года.
  16. . Дата обращения: 28 марта 2016. 29 ноября 2014 года.
  17. . Дата обращения: 28 марта 2016. 27 апреля 2015 года.
  18. . Дата обращения: 31 мая 2022. 16 ноября 2017 года.
  19. . Дата обращения: 20 августа 2018. 13 сентября 2017 года.
  20. . Дата обращения: 28 марта 2016. 10 августа 2015 года.
  21. . Дата обращения: 20 августа 2018. 12 сентября 2017 года.
  22. . Дата обращения: 20 августа 2018. 8 сентября 2017 года.
  23. [ Refining Expression Evaluation Order for Idiomatic C++] . Дата обращения: 23 августа 2018. 26 августа 2018 года.
  24. . Дата обращения: 28 марта 2016. 12 марта 2016 года.
  25. . Дата обращения: 23 августа 2018. 5 октября 2017 года.
  26. . Дата обращения: 28 марта 2016. 29 ноября 2014 года.
  27. . Дата обращения: 28 марта 2016. 29 ноября 2014 года.
  28. . Дата обращения: 28 марта 2016. 28 октября 2015 года.
  29. . Дата обращения: 20 августа 2018. 16 января 2018 года.
  30. . Дата обращения: 15 августа 2019. 11 ноября 2019 года.
  31. . Дата обращения: 28 марта 2016. 11 августа 2015 года.
  32. Так, авторы PVS-Studio часто жаловались на ошибку: программист вместо clear() писал empty() .
  33. . Дата обращения: 28 марта 2016. 11 августа 2015 года.
  34. от 8 сентября 2020 на Wayback Machine en.cppreference.com
  35. . Дата обращения: 28 марта 2016. 3 августа 2015 года.
  36. . Дата обращения: 28 марта 2016. 6 марта 2016 года.
  37. . Дата обращения: 20 августа 2018. 7 октября 2017 года.
  38. . Дата обращения: 28 марта 2016. 4 апреля 2015 года.
  39. . Дата обращения: 12 июня 2019. 22 августа 2017 года.
  40. . Дата обращения: 12 июня 2019. 6 октября 2017 года.
  41. . Дата обращения: 7 августа 2020. 16 сентября 2017 года.
  42. . Дата обращения: 7 августа 2020. 22 августа 2017 года.
  43. . Дата обращения: 7 августа 2020. 9 декабря 2017 года.
  44. . Дата обращения: 18 декабря 2016. 13 ноября 2016 года.
  45. . Дата обращения: 28 марта 2016. 9 марта 2015 года.
  46. . Дата обращения: 1 января 2020. 14 октября 2017 года.
  47. . Дата обращения: 1 января 2020. 28 августа 2017 года.
  48. . Дата обращения: 1 января 2020. 10 октября 2017 года.
  49. . Дата обращения: 1 января 2020. 5 июля 2017 года.
  50. .
  51. . Дата обращения: 30 августа 2019. 30 августа 2019 года.
  52. . Дата обращения: 28 марта 2016. 27 апреля 2015 года.
  53. . Дата обращения: 20 августа 2019. 17 сентября 2019 года.
  54. . Дата обращения: 28 марта 2016. 5 апреля 2016 года.
  55. . Дата обращения: 30 августа 2019. 28 марта 2019 года.
  56. . Дата обращения: 30 августа 2019. 28 марта 2019 года.
  57. . Дата обращения: 30 августа 2019. 30 августа 2019 года.
  58. . Дата обращения: 28 марта 2016. 20 июля 2016 года.
  59. . Дата обращения: 30 августа 2019. 25 августа 2019 года.
  60. . Дата обращения: 30 августа 2019. 30 августа 2019 года.
  61. . Дата обращения: 30 августа 2019. 30 августа 2019 года.
  62. . Дата обращения: 30 августа 2019. 30 августа 2019 года.
  63. . Дата обращения: 1 января 2020. 6 октября 2017 года.
  64. . Дата обращения: 5 февраля 2021. 12 ноября 2020 года.
  65. . Дата обращения: 28 марта 2016. 5 апреля 2016 года.
  66. . Дата обращения: 5 февраля 2021. 24 января 2021 года.
Источник —

Same as C++17