[Высокая производительность, Программирование, C++] Производительность компилятора при работе с концептами в C++20
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, меня зовут Александр, я старший разработчик ПО в Центре разработки Orion Innovation. Хочу признаться, я люблю рассказывать про C++ и не только на различных митапах и конференциях. И вот я добрался до Хабра. На CppConf Russia Piter 2020 я рассказывал про концепты и после выступления получил очень много вопросов про производительность компилятора при работе с ними. Замеры производительности не были целью моего доклада: мне было известно, что концепты компилируются с примерно такой же скоростью, что и обычные метапрограммы, а до детального сравнения я смог добраться совершенно недавно. Спешу поделиться результатом! Несколько слов о концептахКонцепты — переосмысление метапрограммирования, аналогичное constexpr. Если constexpr — это про вычисление выражений во время компиляции, будь то факториал, экспонента и так далее, то концепты — это про перегрузки, специализации, условия существования сущностей. В общем, про «чистое метапрограммирование». Иными словами, в C++20 появилась возможность писать конструкции без единой, привычной для нас треугольной скобки, тем самым получая возможность быстро и читаемо описать какую-либо перегрузку или специализацию:
// #1
void increment(auto & arg) requires requires { ++arg; };
// #2
void increment(auto &);
struct Incrementable { Incrementable & operator++() { return *this; } };
struct NonIncrementable {};
void later() {
Incrementable i;
NonIncrementable ni;
increment(i); // Вызывается #1
increment(ni); // Вызывается #2
}
О том, как всё это работает, есть море информации, например, отличный гайд "Концепты: упрощаем реализацию классов STD Utility" по мотивам выступления Андрея Давыдова на C++ Russia 2019. Ну а мы сфокусируемся на том, какой ценой достигается подобный функционал, чтобы убедиться, что это не только просто, быстро и красиво, но ещё и эффективно. Описание экспериментаИтак, мы будем наблюдать за следующими показателями:
- Время компиляции
- Размер объектного файла
- Количество символов в записи (или же количество кода), в некоторых случаях
Прежде чем мы начнём несколько важных уточнений:
- Во-первых, при подсчёте количества символов в записи мы будем считать все не пустые.
- Во-вторых, в данной статье мы посмотрим лишь на самые простые (буквально несколько строк) случаи, чтобы быть уверенными на 100%, что мы сравниваем абсолютно аналогичные фрагменты кода.
- В-третьих, поскольку компилируемые примеры крайне просты, время компиляции выражается в десятках миллисекунд. Чтобы исключить погрешность, для времени компиляции мы будем использовать усреднённые значения за 100 запусков.
В замерах будут участвовать clang 12.0.0 и g++ 10.3.0, как с полной оптимизацией, так и без неё.В качестве операционной системы выступит Ubuntu 16.04, запущенная на Windows 10 через WSL2. На всякий случай прилагаю характеристики ПК:Характеристики ПК
------------------
System Information
------------------
Operating System: Windows 10 Enterprise 64-bit (10.0, Build 19043) (19041.vb_release.191206-1406)
Language: Russian (Regional Setting: Russian)
System Manufacturer: Dell Inc.
System Model: Latitude 5491
BIOS: 1.12.0 (type: UEFI)
Processor: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz (8 CPUs), ~2.3GHz
Memory: 32768MB RAM
Available OS Memory: 32562MB RAM
Page File: 9995MB used, 27430MB available
------------------------
Disk & DVD/CD-ROM Drives
------------------------
Drive: C:
Free Space: 26.5 GB
Total Space: 243.0 GB
File System: NTFS
Model: SAMSUNG SSD PM871b M.2 2280 256GB
ЭкспериментыПосле необходимых отступлений мы можем, наконец, начать эксперименты. Эксперимент №1: Эволюция метапрограммированияДля начала посмотрим на то, как компиляторы справляются с созданием перегрузки функции для инкрементируемых и неинкрементируемых типов данных аргумента. Компилируемый код для C++ 03, 17 и 20 представлены ниже. Один из показателей, а именно — объем кода, можно оценить уже сейчас: видно, что количество кода существенно сокращается по мере эволюции языка, уступая место читаемости и простоте. Кодincrementable_03.cpp
// copied from boost
template<bool C, typename T = void>
struct enable_if { typedef T type; };
template<typename T>
struct enable_if<false, T> {};
namespace is_inc {
typedef char (&yes)[1]; typedef char (&no)[2];
struct tag {};
struct any { template <class T> any(T const&); };
tag operator++(any const &);
template<typename T>
static yes test(T const &);
static no test(tag);
template<typename _T> struct IsInc
{
static _T & type_value;
static const bool value = sizeof(yes) == sizeof(test(++type_value));
};
}
template<typename T>
struct IsInc : public is_inc::IsInc<T> {};
template<class Ty>
typename enable_if<IsInc<Ty>::value>::type increment(Ty &);
template<class Ty>
typename enable_if<!IsInc<Ty>::value>::type increment(Ty &);
struct Incrementable { Incrementable & operator++() { return *this; } };
struct NonIncrementable {};
void later() {
Incrementable i;
NonIncrementable ni;
increment(i);
increment(ni);
}
incrementable_17.cpp
#include <type_traits>
template<class, class = std::void_t<>>
struct IsInc : std::false_type {};
template<class T>
struct IsInc<T, std::void_t<decltype( ++std::declval<T&>() )>>
: std::true_type
{};
template<class Ty>
std::enable_if_t<IsInc<Ty>::value> increment(Ty &);
template<class Ty>
std::enable_if_t<!IsInc<Ty>::value> increment(Ty &);
struct Incrementable { Incrementable & operator++() { return *this; } };
struct NonIncrementable {};
void later() {
Incrementable i;
NonIncrementable ni;
increment(i);
increment(ni);
}
incrementable_20.cpp
void increment(auto & arg) requires requires { ++arg; };
void increment(auto &);
struct Incrementable { Incrementable & operator++() { return *this; } };
struct NonIncrementable {};
void later() {
Incrementable i;
NonIncrementable ni;
increment(i);
increment(ni);
}
Давайте взглянем на результаты:ФайлКомпиляцияВремя, мсРазмер объектного файла, байтКоличество символов, штincrementable_03.cppclangO043,021304782incrementable_17.cppclangO067,461320472incrementable_20.cppclangO043,421304230incrementable_03.cppclangO347,211296782incrementable_17.cppclangO377,771304472incrementable_20.cppclangO345,701288230incrementable_03.cppgccO019,891568782incrementable_17.cppgccO034,711568472incrementable_20.cppgccO017,621480230incrementable_03.cppgccO318,441552782incrementable_17.cppgccO338,941552472incrementable_20.cppgccO318,571464230 Как уже отмечалось ранее, количество кода существенно уменьшается по мере развития языка: c 782 до 472 и затем до 230. Разница почти в 3,5 раза, если сравнить С++20 и С++03 (на самом деле даже больше, т.к. порядка 150‒170 символов во всех примерах — тестирующий код). Размеры объектного файла также постепенно уменьшаются. Что же со временем компиляции? Странно, но время компиляции 03 и 20 примерно равно, а вот в С++17 — в два раза больше. Давайте взглянем на код наших примеров: помимо всего прочего, в глаза бросается #include в случае C++17. Давайте реализуем declval, enable_if и void_t и проверим: incrementable_no_tt.cpp
template<bool C, typename T = void>
struct enable_if { typedef T type; };
template<typename T>
struct enable_if<false, T> {};
template<bool B, typename T = void>
using enable_if_t = typename enable_if<B, T>::type;
template<typename ...>
using void_t = void;
template<class T>
T && declval() noexcept;
template<class, class = void_t<>>
struct IsInc {
constexpr static bool value = false;
};
template<class T>
struct IsInc<T, void_t<decltype( ++declval<T&>() )>>
{
constexpr static bool value = true;
};
template<class Ty>
enable_if_t<IsInc<Ty>::value> increment(Ty &);
template<class Ty>
enable_if_t<!IsInc<Ty>::value> increment(Ty &);
struct Incrementable { Incrementable & operator++() { return *this; } };
struct NonIncrementable {};
void later() {
Incrementable i;
NonIncrementable ni;
increment(i);
increment(ni);
}
И давайте обновим нашу таблицу:ФайлКомпиляцияВремя, мсРазмер объектного файла, байтКоличество символов, штincrementable_03.cppclangO043,021304782incrementable_17_no_tt.cppclangO044,4981320714incrementable_20.cppclangO043,4191304230incrementable_03.cppclangO347,2051296782incrementable_17_no_tt.cppclangO347,3271312714incrementable_20.cppclangO345,7041288230incrementable_03.cppgccO019,8851568782incrementable_17_no_tt.cppgccO021,1631584714incrementable_20.cppgccO017,6191480230incrementable_03.cppgccO318,4421552782incrementable_17_no_tt.cppgccO319,0571568714incrementable_20.cppgccO318,5661464230 Время компиляции на 17 стандарте нормализовалось и стало практически равно времени компиляции 03 и 20, однако количество кода стало близко к самому тяжёлому, базовому варианту. Так что, если у вас есть под рукой C++20 и нужно написать какую-то простую мета-перегрузку, смело можно использовать концепты. Это читабельнее, компилируется примерно с такой же скоростью, а результат компиляции занимает меньше места. Эксперимент №2: Ограничения для методовДавайте взглянем на еще одну особенность: ограничение для функции или метода (в том числе и для конструкторов и деструкторов) на примере типа OptionalLike, имеющего деструктор по умолчанию в случае, если помещаемый объект тривиален, а иначе — деструктор, выполняющий деинициализацию корректно. Код представлен ниже: Кодoptional_like_17.cpp
#include <type_traits>
#include <string>
template<typename T, typename = void>
struct OptionalLike {
~OptionalLike() {
/* Calls d-tor manually */
}
};
template<typename T>
struct OptionalLike<T, std::enable_if_t<std::is_trivially_destructible<T>::value>>
{
~OptionalLike() = default;
};
void later() {
OptionalLike<int> oli;
OptionalLike<std::string> ols;
}
optional_like_20.cpp
#include <type_traits>
#include <string>
template<typename T>
struct OptionalLike
{
~OptionalLike() {
/* Calls d-tor manually */
}
~OptionalLike() requires (std::is_trivially_destructible<T>::value) = default;
};
void later() {
OptionalLike<int> oli;
OptionalLike<std::string> ols;
}
Давайте взглянем на результаты:ФайлКомпиляцияВремя, мсРазмер объектного файла, байтКоличество символов, штoptional_like_17.cppclangO0487,621424319optional_like_20.cppclangO0616,81816253optional_like_17.cppclangO3490,07944319optional_like_20.cppclangO3627,641024253optional_like_17.cppgccO0202,291968319optional_like_20.cppgccO0505,821968253optional_like_17.cppgccO3205,551200319optional_like_20.cppgccO3524,541200253 Мы видим, что новый вариант выглядит более читабельным и лаконичным (253 символа против 319 у классического), однако платим за это временем компиляции: оба компилятора как с оптимизацией, так и без показали худшее время компиляции в случае с концептами. GCC аж в 2‒2,5 раза медленнее. При этом размер объектного файла у gcc не изменяется вовсе, а в случае clang — больше для концептов. Классический компромисс: либо меньше кода, но дольше компиляция, либо больше кода, но быстрее компиляция.Эксперимент №3: Влияние использования концептов на время компиляцииМы знаем, что накладывать ограничения на тип можно используя именованные наборы требований, они же концепты. Также можно указать требования непосредственно в момент объявления шаблонной сущности. Давайте посмотрим, есть ли разница с точки зрения компилятора. Компилировать будем следующие фрагменты: Кодinline.cpp
template<typename T>
void foo() requires (sizeof(T) >= 4) { }
template<typename T>
void foo() {}
void later() {
foo<char>();
foo<int>();
}
concept.cpp
template<typename T>
concept IsBig = sizeof(T) >= 4;
template<typename T>
void foo() requires IsBig<T> { }
template<typename T>
void foo() {}
void later() {
foo<char>();
foo<int>();
}
Сразу взглянем на результаты: ФайлКомпиляцияВремя, мсРазмер объектного файла, байтinline.cppclangO038,6661736concept.cppclangO039,8681736concept.cppclangO342,5781040inline.cppclangO343,6101040inline.cppgccO014,5981976concept.cppgccO014,6401976concept.cppgccO314,8721224inline.cppgccO314,9511224 Как мы можем заметить, размеры получившихся объектных файлов идентичны, а показатели времени компиляции практически совпадают. Так что при выборе концепт или inline-требование можно не задумываться о производительности компилятора. Эксперимент №4: Варианты ограничения функции Теперь посмотрим на варианты наложения ограничения на шаблонные параметры на примере функций. Ограничить функцию можно аж четырьмя способами:
- Имя концепта вместо typename
- Requires clause после template<>
- Имя концепта рядом с auto
- Trailing requires clause
Давайте узнаем, какой же из предложенных способов самый оптимальный с точки зрения компиляции. Компилируемый код представлен ниже:Кодinstead_of_typename.cpp
template<typename T>
concept IsBig = sizeof(T) >= 4;
template<IsBig T>
void foo(T const &) { }
template<typename T>
void foo(T const &) {}
void later() {
foo<char>('a');
foo<int>(1);
}
after_template.cpp
template<typename T>
concept IsBig = sizeof(T) >= 4;
template<typename T>
requires IsBig<T>
void foo(T const &) { }
template<typename T>
void foo(T const &) {}
void later() {
foo<char>('a');
foo<int>(1);
}
with_auto.cpp
template<typename T>
concept IsBig = sizeof(T) >= 4;
template<typename T>
void foo(IsBig auto const &) { }
template<typename T>
void foo(auto const &) {}
void later() {
foo<char>('a');
foo<int>(1);
}
requires_clause.cpp
template<typename T>
concept IsBig = sizeof(T) >= 4;
template<typename T>
void foo(T const &) requires IsBig<T> { }
template<typename T>
void foo(T const &) {}
void later() {
foo<char>('a');
foo<int>(1);
}
А вот и результаты:ФайлКомпиляцияВремя, мсРазмер объектного файла, байтfunction_with_auto.cppclangO040,8781760function_after_template.cppclangO041,9471760function_requires_clause.cppclangO042,5511760function_instead_of_typename.cppclangO046,8931760function_with_auto.cppclangO343,9281024function_requires_clause.cppclangO345,1761032function_after_template.cppclangO345,2751032function_instead_of_typename.cppclangO350,421032function_requires_clause.cppgccO016,5612008function_with_auto.cppgccO016,6922008function_after_template.cppgccO017,0322008function_instead_of_typename.cppgccO017,8022016function_requires_clause.cppgccO316,2331208function_with_auto.cppgccO316,7111208function_after_template.cppgccO317,2161208function_instead_of_typename.cppgccO318,3151216 Как мы видим, время компиляции отличается незначительно, однако мы можем заметить следующее:
- Вариант с использованием имени концепта вместо typename оказался самым медленным во всех случаях.
- Варианты trailing requires clause или использование концепта рядом с auto оказались самыми быстрыми.
- Варианты, где присутствует template<>на 5‒10% медленнее остальных.
- Размеры объектных файлов изменяются незначительно, однако вариант с именем концепта вместо typename оказался самым объемным в случае gcc, а вариант с auto оказался наименее объемным в случае clang.
Эксперимент №5: Влияние сложности концепта на время компиляцииПоследнее, что мы рассмотрим в рамках данной статьи, и, наверное, самое интересное: влияние сложности концепта на время компиляции. Давайте возьмём и скомпилируем следующие примеры, где сложность используемого концепта (количество проверок или условий) возрастает от первого к последнему. Кодconcept_complexity_1.cpp
template<typename T>
concept ConceptA = sizeof(T) >= 1;
template<typename T>
concept TestedConcept = ConceptA<T>;
void foo(TestedConcept auto const &) {}
void foo(auto const &) {}
void later() {
int i { 0 };
int * ip = &i;
foo(i);
foo(ip);
}
concept_complexity_2.cpp
template<typename T>
concept ConceptA = sizeof(T) >= 1;
template<typename T>
concept ConceptB = requires(T i, int x) {
{ i++ } noexcept -> ConceptA;
{ ++i } noexcept -> ConceptA;
{ i-- } noexcept -> ConceptA;
{ --i } noexcept -> ConceptA;
{ i + i } noexcept -> ConceptA;
{ i - i } noexcept -> ConceptA;
{ i += i } noexcept -> ConceptA;
{ i -= i } noexcept -> ConceptA;
{ i * i } noexcept -> ConceptA;
{ i / i } noexcept -> ConceptA;
{ i % i } noexcept -> ConceptA;
{ i *= i } noexcept -> ConceptA;
{ i /= i } noexcept -> ConceptA;
{ i %= i } noexcept -> ConceptA;
{ i | i } noexcept -> ConceptA;
{ i & i } noexcept -> ConceptA;
{ i |= i } noexcept -> ConceptA;
{ i &= i } noexcept -> ConceptA;
{ ~i } noexcept -> ConceptA;
{ i ^ i } noexcept -> ConceptA;
{ i << x } noexcept -> ConceptA;
{ i >> x } noexcept -> ConceptA;
{ i ^= i } noexcept -> ConceptA;
{ i <<= x } noexcept -> ConceptA;
{ i >>= x } noexcept -> ConceptA;
};
template<typename T>
concept ConceptC = requires(T i, int x) {
{ i++ } noexcept -> ConceptB;
{ ++i } noexcept -> ConceptB;
{ i-- } noexcept -> ConceptB;
{ --i } noexcept -> ConceptB;
{ i + i } noexcept -> ConceptB;
{ i - i } noexcept -> ConceptB;
{ i += i } noexcept -> ConceptB;
{ i -= i } noexcept -> ConceptB;
{ i * i } noexcept -> ConceptB;
{ i / i } noexcept -> ConceptB;
{ i % i } noexcept -> ConceptB;
{ i *= i } noexcept -> ConceptB;
{ i /= i } noexcept -> ConceptB;
{ i %= i } noexcept -> ConceptB;
{ i | i } noexcept -> ConceptB;
{ i & i } noexcept -> ConceptB;
{ i |= i } noexcept -> ConceptB;
{ i &= i } noexcept -> ConceptB;
{ ~i } noexcept -> ConceptB;
{ i ^ i } noexcept -> ConceptB;
{ i << x } noexcept -> ConceptB;
{ i >> x } noexcept -> ConceptB;
{ i ^= i } noexcept -> ConceptB;
{ i <<= x } noexcept -> ConceptB;
{ i >>= x } noexcept -> ConceptB;
};
template<typename T>
concept ConceptD = requires(T i, int x) {
{ i++ } noexcept -> ConceptC;
{ ++i } noexcept -> ConceptC;
{ i-- } noexcept -> ConceptC;
{ --i } noexcept -> ConceptC;
{ i + i } noexcept -> ConceptC;
{ i - i } noexcept -> ConceptC;
{ i += i } noexcept -> ConceptC;
{ i -= i } noexcept -> ConceptC;
{ i * i } noexcept -> ConceptC;
{ i / i } noexcept -> ConceptC;
{ i % i } noexcept -> ConceptC;
{ i *= i } noexcept -> ConceptC;
{ i /= i } noexcept -> ConceptC;
{ i %= i } noexcept -> ConceptC;
{ i | i } noexcept -> ConceptC;
{ i & i } noexcept -> ConceptC;
{ i |= i } noexcept -> ConceptC;
{ i &= i } noexcept -> ConceptC;
{ ~i } noexcept -> ConceptC;
{ i ^ i } noexcept -> ConceptC;
{ i << x } noexcept -> ConceptC;
{ i >> x } noexcept -> ConceptC;
{ i ^= i } noexcept -> ConceptC;
{ i <<= x } noexcept -> ConceptC;
{ i >>= x } noexcept -> ConceptC;
};
template<typename T>
concept TestedConcept = ConceptA<T> && ConceptB<T> && ConceptC<T> && ConceptD<T>;
void foo(TestedConcept auto const &) {}
void foo(auto const &) {}
void later() {
int i { 0 };
int * ip = &i;
foo(i);
foo(ip);
}
concept_complexity_3.cpp
template<typename T>
concept ConceptA = sizeof(T) >= 1;
template<typename T>
concept ConceptB = requires(T i, int x) {
{ i++ } noexcept -> ConceptA;
{ ++i } noexcept -> ConceptA;
{ i-- } noexcept -> ConceptA;
{ --i } noexcept -> ConceptA;
{ i + i } noexcept -> ConceptA;
{ i - i } noexcept -> ConceptA;
{ i += i } noexcept -> ConceptA;
{ i -= i } noexcept -> ConceptA;
{ i * i } noexcept -> ConceptA;
{ i / i } noexcept -> ConceptA;
{ i % i } noexcept -> ConceptA;
{ i *= i } noexcept -> ConceptA;
{ i /= i } noexcept -> ConceptA;
{ i %= i } noexcept -> ConceptA;
{ i | i } noexcept -> ConceptA;
{ i & i } noexcept -> ConceptA;
{ i |= i } noexcept -> ConceptA;
{ i &= i } noexcept -> ConceptA;
{ ~i } noexcept -> ConceptA;
{ i ^ i } noexcept -> ConceptA;
{ i << x } noexcept -> ConceptA;
{ i >> x } noexcept -> ConceptA;
{ i ^= i } noexcept -> ConceptA;
{ i <<= x } noexcept -> ConceptA;
{ i >>= x } noexcept -> ConceptA;
};
template<typename T>
concept ConceptC = requires(T i, int x) {
{ i++ } noexcept -> ConceptB;
{ ++i } noexcept -> ConceptB;
{ i-- } noexcept -> ConceptB;
{ --i } noexcept -> ConceptB;
{ i + i } noexcept -> ConceptB;
{ i - i } noexcept -> ConceptB;
{ i += i } noexcept -> ConceptB;
{ i -= i } noexcept -> ConceptB;
{ i * i } noexcept -> ConceptB;
{ i / i } noexcept -> ConceptB;
{ i % i } noexcept -> ConceptB;
{ i *= i } noexcept -> ConceptB;
{ i /= i } noexcept -> ConceptB;
{ i %= i } noexcept -> ConceptB;
{ i | i } noexcept -> ConceptB;
{ i & i } noexcept -> ConceptB;
{ i |= i } noexcept -> ConceptB;
{ i &= i } noexcept -> ConceptB;
{ ~i } noexcept -> ConceptB;
{ i ^ i } noexcept -> ConceptB;
{ i << x } noexcept -> ConceptB;
{ i >> x } noexcept -> ConceptB;
{ i ^= i } noexcept -> ConceptB;
{ i <<= x } noexcept -> ConceptB;
{ i >>= x } noexcept -> ConceptB;
};
template<typename T>
concept ConceptD = requires(T i, int x) {
{ i++ } noexcept -> ConceptC;
{ ++i } noexcept -> ConceptC;
{ i-- } noexcept -> ConceptC;
{ --i } noexcept -> ConceptC;
{ i + i } noexcept -> ConceptC;
{ i - i } noexcept -> ConceptC;
{ i += i } noexcept -> ConceptC;
{ i -= i } noexcept -> ConceptC;
{ i * i } noexcept -> ConceptC;
{ i / i } noexcept -> ConceptC;
{ i % i } noexcept -> ConceptC;
{ i *= i } noexcept -> ConceptC;
{ i /= i } noexcept -> ConceptC;
{ i %= i } noexcept -> ConceptC;
{ i | i } noexcept -> ConceptC;
{ i & i } noexcept -> ConceptC;
{ i |= i } noexcept -> ConceptC;
{ i &= i } noexcept -> ConceptC;
{ ~i } noexcept -> ConceptC;
{ i ^ i } noexcept -> ConceptC;
{ i << x } noexcept -> ConceptC;
{ i >> x } noexcept -> ConceptC;
{ i ^= i } noexcept -> ConceptC;
{ i <<= x } noexcept -> ConceptC;
{ i >>= x } noexcept -> ConceptC;
};
template<typename T>
concept ConceptE = requires(T i, int x) {
{ i++ } noexcept -> ConceptD;
{ ++i } noexcept -> ConceptD;
{ i-- } noexcept -> ConceptD;
{ --i } noexcept -> ConceptD;
{ i + i } noexcept -> ConceptD;
{ i - i } noexcept -> ConceptD;
{ i += i } noexcept -> ConceptD;
{ i -= i } noexcept -> ConceptD;
{ i * i } noexcept -> ConceptD;
{ i / i } noexcept -> ConceptD;
{ i % i } noexcept -> ConceptD;
{ i *= i } noexcept -> ConceptD;
{ i /= i } noexcept -> ConceptD;
{ i %= i } noexcept -> ConceptD;
{ i | i } noexcept -> ConceptD;
{ i & i } noexcept -> ConceptD;
{ i |= i } noexcept -> ConceptD;
{ i &= i } noexcept -> ConceptD;
{ ~i } noexcept -> ConceptD;
{ i ^ i } noexcept -> ConceptD;
{ i << x } noexcept -> ConceptD;
{ i >> x } noexcept -> ConceptD;
{ i ^= i } noexcept -> ConceptD;
{ i <<= x } noexcept -> ConceptD;
{ i >>= x } noexcept -> ConceptD;
};
template<typename T>
concept ConceptF = requires(T i, int x) {
{ i++ } noexcept -> ConceptE;
{ ++i } noexcept -> ConceptE;
{ i-- } noexcept -> ConceptE;
{ --i } noexcept -> ConceptE;
{ i + i } noexcept -> ConceptE;
{ i - i } noexcept -> ConceptE;
{ i += i } noexcept -> ConceptE;
{ i -= i } noexcept -> ConceptE;
{ i * i } noexcept -> ConceptE;
{ i / i } noexcept -> ConceptE;
{ i % i } noexcept -> ConceptE;
{ i *= i } noexcept -> ConceptE;
{ i /= i } noexcept -> ConceptE;
{ i %= i } noexcept -> ConceptE;
{ i | i } noexcept -> ConceptE;
{ i & i } noexcept -> ConceptE;
{ i |= i } noexcept -> ConceptE;
{ i &= i } noexcept -> ConceptE;
{ ~i } noexcept -> ConceptE;
{ i ^ i } noexcept -> ConceptE;
{ i << x } noexcept -> ConceptE;
{ i >> x } noexcept -> ConceptE;
{ i ^= i } noexcept -> ConceptE;
{ i <<= x } noexcept -> ConceptE;
{ i >>= x } noexcept -> ConceptE;
};
template<typename T>
concept ConceptG = requires(T i, int x) {
{ i++ } noexcept -> ConceptF;
{ ++i } noexcept -> ConceptF;
{ i-- } noexcept -> ConceptF;
{ --i } noexcept -> ConceptF;
{ i + i } noexcept -> ConceptF;
{ i - i } noexcept -> ConceptF;
{ i += i } noexcept -> ConceptF;
{ i -= i } noexcept -> ConceptF;
{ i * i } noexcept -> ConceptF;
{ i / i } noexcept -> ConceptF;
{ i % i } noexcept -> ConceptF;
{ i *= i } noexcept -> ConceptF;
{ i /= i } noexcept -> ConceptF;
{ i %= i } noexcept -> ConceptF;
{ i | i } noexcept -> ConceptF;
{ i & i } noexcept -> ConceptF;
{ i |= i } noexcept -> ConceptF;
{ i &= i } noexcept -> ConceptF;
{ ~i } noexcept -> ConceptF;
{ i ^ i } noexcept -> ConceptF;
{ i << x } noexcept -> ConceptF;
{ i >> x } noexcept -> ConceptF;
{ i ^= i } noexcept -> ConceptF;
{ i <<= x } noexcept -> ConceptF;
{ i >>= x } noexcept -> ConceptF;
};
template<typename T>
concept TestedConcept = ConceptA<T> && ConceptB<T> && ConceptC<T> && ConceptD<T> &&
ConceptE<T> && ConceptF<T> && ConceptG<T>;
void foo(TestedConcept auto const &) {}
void foo(auto const &) {}
void later() {
int i { 0 };
int * ip = &i;
foo(i);
foo(ip);
}
Давайте взглянем на результат:ФайлКомпиляцияВремя, мсКоличество символов, штconcept_complexity_1.cppclangO037,441201concept_complexity_2.cppclangO038,2112244concept_complexity_3.cppclangO039,9894287concept_complexity_1.cppclangO340,062201concept_complexity_2.cppclangO340,6592244concept_complexity_3.cppclangO343,3144287concept_complexity_1.cppgccO015,352201concept_complexity_2.cppgccO016,0772244concept_complexity_3.cppgccO018,0914287concept_complexity_1.cppgccO315,243201concept_complexity_2.cppgccO317,5522244concept_complexity_3.cppgccO318,514287Чего и следовало ожидать, в общем случае существенное увеличение сложности концепта (обратите внимание, что концепты в примерах рекурсивные, и каждый последующий включает многократные отсылки к предыдущим) приводит к увеличению времени компиляции лишь на 5‒15%.ЗаключениеВ результате вышеописанных экспериментов мы можем сделать следующие выводы:
- Концепты позволяют создавать более читабельный код, который компилируется в меньший объектный файл, по сравнению с классическим метапрограммированием.
- Несмотря на это, код, содержащий концепты/constraint’ы зачастую компилируется дольше, иногда довольно значительно, как это было в случае ограничения для методов.
- Время компиляции прямо пропорционально сложности концептов/constraint'ов.
Post ScriptumВо-первых, к статье прилагаю ссылку на гитхаб, пройдя по которой вы можете найти скрипты для запуска тестов, а также используемые в статье фрагменты кода и повторить некоторые (а может и все) тесты локально. Ну а во-вторых, мне бы очень хотелось увидеть, как ведут себя компиляторы с более сложными конструкциями. Если вы знаете/придумали подходящие примеры, смело пишите о них в комментариях, и я с радостью произведу замеры.
===========
Источник:
habr.com
===========
Похожие новости:
- [Высокая производительность, Разработка веб-сайтов, MySQL, Go, Big Data] Как мы весь интернет сканировали
- [Программирование, .NET, PowerShell, Алгоритмы, Разработка под Windows] Powershell настоящий язык программирования. Скрипт оптимизации рутины в техподдержке
- [Программирование, Java, Промышленное программирование, Тестирование веб-сервисов] Как подружить Redis Cluster c Testcontainers?
- [Программирование] Локальное время и дата рождения или зачем UTC
- [Программирование, Венчурные инвестиции, Развитие стартапа, Образование за рубежом, Бизнес-модели] Перевод Курса по стартапам и бизнесу от Стэнфордского Университета. Лекция №2. Команда и реализация стартапа (перевод)
- [Программирование микроконтроллеров] Программируемое реле easyE4
- [Высокая производительность, Тестирование IT-систем, IT-компании] Следствие вели: пропажа FC-линков HBA Emulex на сервере Atos BullSequana S1600
- [Разработка веб-сайтов, Программирование, Haskell, Функциональное программирование] Создаем веб-приложение на Haskell с использованием Reflex. Часть 4
- [Open source, Программирование, Системное программирование, Компиляторы, Rust] Rust 1.53.0: IntoIterator для массивов, "|" в шаблонах, Unicode-идентификаторы, поддержка имени HEAD-ветки в Cargo (перевод)
- [Программирование, Геоинформационные сервисы, Математика, Научно-популярное, Физика] Оцениваем открытые и коммерческие цифровые модели рельефа
Теги для поиска: #_vysokaja_proizvoditelnost (Высокая производительность), #_programmirovanie (Программирование), #_c++, #_c++, #_kontsepty (концепты), #_proizvoditelnost (производительность), #_eksperimenty (эксперименты), #_zamery (замеры), #_blog_kompanii_orion_innovation (
Блог компании Orion Innovation
), #_vysokaja_proizvoditelnost (
Высокая производительность
), #_programmirovanie (
Программирование
), #_c++
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:18
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, меня зовут Александр, я старший разработчик ПО в Центре разработки Orion Innovation. Хочу признаться, я люблю рассказывать про C++ и не только на различных митапах и конференциях. И вот я добрался до Хабра. На CppConf Russia Piter 2020 я рассказывал про концепты и после выступления получил очень много вопросов про производительность компилятора при работе с ними. Замеры производительности не были целью моего доклада: мне было известно, что концепты компилируются с примерно такой же скоростью, что и обычные метапрограммы, а до детального сравнения я смог добраться совершенно недавно. Спешу поделиться результатом! Несколько слов о концептахКонцепты — переосмысление метапрограммирования, аналогичное constexpr. Если constexpr — это про вычисление выражений во время компиляции, будь то факториал, экспонента и так далее, то концепты — это про перегрузки, специализации, условия существования сущностей. В общем, про «чистое метапрограммирование». Иными словами, в C++20 появилась возможность писать конструкции без единой, привычной для нас треугольной скобки, тем самым получая возможность быстро и читаемо описать какую-либо перегрузку или специализацию: // #1
void increment(auto & arg) requires requires { ++arg; }; // #2 void increment(auto &); struct Incrementable { Incrementable & operator++() { return *this; } }; struct NonIncrementable {}; void later() { Incrementable i; NonIncrementable ni; increment(i); // Вызывается #1 increment(ni); // Вызывается #2 }
------------------
System Information ------------------ Operating System: Windows 10 Enterprise 64-bit (10.0, Build 19043) (19041.vb_release.191206-1406) Language: Russian (Regional Setting: Russian) System Manufacturer: Dell Inc. System Model: Latitude 5491 BIOS: 1.12.0 (type: UEFI) Processor: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz (8 CPUs), ~2.3GHz Memory: 32768MB RAM Available OS Memory: 32562MB RAM Page File: 9995MB used, 27430MB available ------------------------ Disk & DVD/CD-ROM Drives ------------------------ Drive: C: Free Space: 26.5 GB Total Space: 243.0 GB File System: NTFS Model: SAMSUNG SSD PM871b M.2 2280 256GB // copied from boost
template<bool C, typename T = void> struct enable_if { typedef T type; }; template<typename T> struct enable_if<false, T> {}; namespace is_inc { typedef char (&yes)[1]; typedef char (&no)[2]; struct tag {}; struct any { template <class T> any(T const&); }; tag operator++(any const &); template<typename T> static yes test(T const &); static no test(tag); template<typename _T> struct IsInc { static _T & type_value; static const bool value = sizeof(yes) == sizeof(test(++type_value)); }; } template<typename T> struct IsInc : public is_inc::IsInc<T> {}; template<class Ty> typename enable_if<IsInc<Ty>::value>::type increment(Ty &); template<class Ty> typename enable_if<!IsInc<Ty>::value>::type increment(Ty &); struct Incrementable { Incrementable & operator++() { return *this; } }; struct NonIncrementable {}; void later() { Incrementable i; NonIncrementable ni; increment(i); increment(ni); } #include <type_traits>
template<class, class = std::void_t<>> struct IsInc : std::false_type {}; template<class T> struct IsInc<T, std::void_t<decltype( ++std::declval<T&>() )>> : std::true_type {}; template<class Ty> std::enable_if_t<IsInc<Ty>::value> increment(Ty &); template<class Ty> std::enable_if_t<!IsInc<Ty>::value> increment(Ty &); struct Incrementable { Incrementable & operator++() { return *this; } }; struct NonIncrementable {}; void later() { Incrementable i; NonIncrementable ni; increment(i); increment(ni); } void increment(auto & arg) requires requires { ++arg; };
void increment(auto &); struct Incrementable { Incrementable & operator++() { return *this; } }; struct NonIncrementable {}; void later() { Incrementable i; NonIncrementable ni; increment(i); increment(ni); } template<bool C, typename T = void>
struct enable_if { typedef T type; }; template<typename T> struct enable_if<false, T> {}; template<bool B, typename T = void> using enable_if_t = typename enable_if<B, T>::type; template<typename ...> using void_t = void; template<class T> T && declval() noexcept; template<class, class = void_t<>> struct IsInc { constexpr static bool value = false; }; template<class T> struct IsInc<T, void_t<decltype( ++declval<T&>() )>> { constexpr static bool value = true; }; template<class Ty> enable_if_t<IsInc<Ty>::value> increment(Ty &); template<class Ty> enable_if_t<!IsInc<Ty>::value> increment(Ty &); struct Incrementable { Incrementable & operator++() { return *this; } }; struct NonIncrementable {}; void later() { Incrementable i; NonIncrementable ni; increment(i); increment(ni); } #include <type_traits>
#include <string> template<typename T, typename = void> struct OptionalLike { ~OptionalLike() { /* Calls d-tor manually */ } }; template<typename T> struct OptionalLike<T, std::enable_if_t<std::is_trivially_destructible<T>::value>> { ~OptionalLike() = default; }; void later() { OptionalLike<int> oli; OptionalLike<std::string> ols; } #include <type_traits>
#include <string> template<typename T> struct OptionalLike { ~OptionalLike() { /* Calls d-tor manually */ } ~OptionalLike() requires (std::is_trivially_destructible<T>::value) = default; }; void later() { OptionalLike<int> oli; OptionalLike<std::string> ols; } template<typename T>
void foo() requires (sizeof(T) >= 4) { } template<typename T> void foo() {} void later() { foo<char>(); foo<int>(); } template<typename T>
concept IsBig = sizeof(T) >= 4; template<typename T> void foo() requires IsBig<T> { } template<typename T> void foo() {} void later() { foo<char>(); foo<int>(); }
template<typename T>
concept IsBig = sizeof(T) >= 4; template<IsBig T> void foo(T const &) { } template<typename T> void foo(T const &) {} void later() { foo<char>('a'); foo<int>(1); } template<typename T>
concept IsBig = sizeof(T) >= 4; template<typename T> requires IsBig<T> void foo(T const &) { } template<typename T> void foo(T const &) {} void later() { foo<char>('a'); foo<int>(1); } template<typename T>
concept IsBig = sizeof(T) >= 4; template<typename T> void foo(IsBig auto const &) { } template<typename T> void foo(auto const &) {} void later() { foo<char>('a'); foo<int>(1); } template<typename T>
concept IsBig = sizeof(T) >= 4; template<typename T> void foo(T const &) requires IsBig<T> { } template<typename T> void foo(T const &) {} void later() { foo<char>('a'); foo<int>(1); }
template<typename T>
concept ConceptA = sizeof(T) >= 1; template<typename T> concept TestedConcept = ConceptA<T>; void foo(TestedConcept auto const &) {} void foo(auto const &) {} void later() { int i { 0 }; int * ip = &i; foo(i); foo(ip); } template<typename T>
concept ConceptA = sizeof(T) >= 1; template<typename T> concept ConceptB = requires(T i, int x) { { i++ } noexcept -> ConceptA; { ++i } noexcept -> ConceptA; { i-- } noexcept -> ConceptA; { --i } noexcept -> ConceptA; { i + i } noexcept -> ConceptA; { i - i } noexcept -> ConceptA; { i += i } noexcept -> ConceptA; { i -= i } noexcept -> ConceptA; { i * i } noexcept -> ConceptA; { i / i } noexcept -> ConceptA; { i % i } noexcept -> ConceptA; { i *= i } noexcept -> ConceptA; { i /= i } noexcept -> ConceptA; { i %= i } noexcept -> ConceptA; { i | i } noexcept -> ConceptA; { i & i } noexcept -> ConceptA; { i |= i } noexcept -> ConceptA; { i &= i } noexcept -> ConceptA; { ~i } noexcept -> ConceptA; { i ^ i } noexcept -> ConceptA; { i << x } noexcept -> ConceptA; { i >> x } noexcept -> ConceptA; { i ^= i } noexcept -> ConceptA; { i <<= x } noexcept -> ConceptA; { i >>= x } noexcept -> ConceptA; }; template<typename T> concept ConceptC = requires(T i, int x) { { i++ } noexcept -> ConceptB; { ++i } noexcept -> ConceptB; { i-- } noexcept -> ConceptB; { --i } noexcept -> ConceptB; { i + i } noexcept -> ConceptB; { i - i } noexcept -> ConceptB; { i += i } noexcept -> ConceptB; { i -= i } noexcept -> ConceptB; { i * i } noexcept -> ConceptB; { i / i } noexcept -> ConceptB; { i % i } noexcept -> ConceptB; { i *= i } noexcept -> ConceptB; { i /= i } noexcept -> ConceptB; { i %= i } noexcept -> ConceptB; { i | i } noexcept -> ConceptB; { i & i } noexcept -> ConceptB; { i |= i } noexcept -> ConceptB; { i &= i } noexcept -> ConceptB; { ~i } noexcept -> ConceptB; { i ^ i } noexcept -> ConceptB; { i << x } noexcept -> ConceptB; { i >> x } noexcept -> ConceptB; { i ^= i } noexcept -> ConceptB; { i <<= x } noexcept -> ConceptB; { i >>= x } noexcept -> ConceptB; }; template<typename T> concept ConceptD = requires(T i, int x) { { i++ } noexcept -> ConceptC; { ++i } noexcept -> ConceptC; { i-- } noexcept -> ConceptC; { --i } noexcept -> ConceptC; { i + i } noexcept -> ConceptC; { i - i } noexcept -> ConceptC; { i += i } noexcept -> ConceptC; { i -= i } noexcept -> ConceptC; { i * i } noexcept -> ConceptC; { i / i } noexcept -> ConceptC; { i % i } noexcept -> ConceptC; { i *= i } noexcept -> ConceptC; { i /= i } noexcept -> ConceptC; { i %= i } noexcept -> ConceptC; { i | i } noexcept -> ConceptC; { i & i } noexcept -> ConceptC; { i |= i } noexcept -> ConceptC; { i &= i } noexcept -> ConceptC; { ~i } noexcept -> ConceptC; { i ^ i } noexcept -> ConceptC; { i << x } noexcept -> ConceptC; { i >> x } noexcept -> ConceptC; { i ^= i } noexcept -> ConceptC; { i <<= x } noexcept -> ConceptC; { i >>= x } noexcept -> ConceptC; }; template<typename T> concept TestedConcept = ConceptA<T> && ConceptB<T> && ConceptC<T> && ConceptD<T>; void foo(TestedConcept auto const &) {} void foo(auto const &) {} void later() { int i { 0 }; int * ip = &i; foo(i); foo(ip); } template<typename T>
concept ConceptA = sizeof(T) >= 1; template<typename T> concept ConceptB = requires(T i, int x) { { i++ } noexcept -> ConceptA; { ++i } noexcept -> ConceptA; { i-- } noexcept -> ConceptA; { --i } noexcept -> ConceptA; { i + i } noexcept -> ConceptA; { i - i } noexcept -> ConceptA; { i += i } noexcept -> ConceptA; { i -= i } noexcept -> ConceptA; { i * i } noexcept -> ConceptA; { i / i } noexcept -> ConceptA; { i % i } noexcept -> ConceptA; { i *= i } noexcept -> ConceptA; { i /= i } noexcept -> ConceptA; { i %= i } noexcept -> ConceptA; { i | i } noexcept -> ConceptA; { i & i } noexcept -> ConceptA; { i |= i } noexcept -> ConceptA; { i &= i } noexcept -> ConceptA; { ~i } noexcept -> ConceptA; { i ^ i } noexcept -> ConceptA; { i << x } noexcept -> ConceptA; { i >> x } noexcept -> ConceptA; { i ^= i } noexcept -> ConceptA; { i <<= x } noexcept -> ConceptA; { i >>= x } noexcept -> ConceptA; }; template<typename T> concept ConceptC = requires(T i, int x) { { i++ } noexcept -> ConceptB; { ++i } noexcept -> ConceptB; { i-- } noexcept -> ConceptB; { --i } noexcept -> ConceptB; { i + i } noexcept -> ConceptB; { i - i } noexcept -> ConceptB; { i += i } noexcept -> ConceptB; { i -= i } noexcept -> ConceptB; { i * i } noexcept -> ConceptB; { i / i } noexcept -> ConceptB; { i % i } noexcept -> ConceptB; { i *= i } noexcept -> ConceptB; { i /= i } noexcept -> ConceptB; { i %= i } noexcept -> ConceptB; { i | i } noexcept -> ConceptB; { i & i } noexcept -> ConceptB; { i |= i } noexcept -> ConceptB; { i &= i } noexcept -> ConceptB; { ~i } noexcept -> ConceptB; { i ^ i } noexcept -> ConceptB; { i << x } noexcept -> ConceptB; { i >> x } noexcept -> ConceptB; { i ^= i } noexcept -> ConceptB; { i <<= x } noexcept -> ConceptB; { i >>= x } noexcept -> ConceptB; }; template<typename T> concept ConceptD = requires(T i, int x) { { i++ } noexcept -> ConceptC; { ++i } noexcept -> ConceptC; { i-- } noexcept -> ConceptC; { --i } noexcept -> ConceptC; { i + i } noexcept -> ConceptC; { i - i } noexcept -> ConceptC; { i += i } noexcept -> ConceptC; { i -= i } noexcept -> ConceptC; { i * i } noexcept -> ConceptC; { i / i } noexcept -> ConceptC; { i % i } noexcept -> ConceptC; { i *= i } noexcept -> ConceptC; { i /= i } noexcept -> ConceptC; { i %= i } noexcept -> ConceptC; { i | i } noexcept -> ConceptC; { i & i } noexcept -> ConceptC; { i |= i } noexcept -> ConceptC; { i &= i } noexcept -> ConceptC; { ~i } noexcept -> ConceptC; { i ^ i } noexcept -> ConceptC; { i << x } noexcept -> ConceptC; { i >> x } noexcept -> ConceptC; { i ^= i } noexcept -> ConceptC; { i <<= x } noexcept -> ConceptC; { i >>= x } noexcept -> ConceptC; }; template<typename T> concept ConceptE = requires(T i, int x) { { i++ } noexcept -> ConceptD; { ++i } noexcept -> ConceptD; { i-- } noexcept -> ConceptD; { --i } noexcept -> ConceptD; { i + i } noexcept -> ConceptD; { i - i } noexcept -> ConceptD; { i += i } noexcept -> ConceptD; { i -= i } noexcept -> ConceptD; { i * i } noexcept -> ConceptD; { i / i } noexcept -> ConceptD; { i % i } noexcept -> ConceptD; { i *= i } noexcept -> ConceptD; { i /= i } noexcept -> ConceptD; { i %= i } noexcept -> ConceptD; { i | i } noexcept -> ConceptD; { i & i } noexcept -> ConceptD; { i |= i } noexcept -> ConceptD; { i &= i } noexcept -> ConceptD; { ~i } noexcept -> ConceptD; { i ^ i } noexcept -> ConceptD; { i << x } noexcept -> ConceptD; { i >> x } noexcept -> ConceptD; { i ^= i } noexcept -> ConceptD; { i <<= x } noexcept -> ConceptD; { i >>= x } noexcept -> ConceptD; }; template<typename T> concept ConceptF = requires(T i, int x) { { i++ } noexcept -> ConceptE; { ++i } noexcept -> ConceptE; { i-- } noexcept -> ConceptE; { --i } noexcept -> ConceptE; { i + i } noexcept -> ConceptE; { i - i } noexcept -> ConceptE; { i += i } noexcept -> ConceptE; { i -= i } noexcept -> ConceptE; { i * i } noexcept -> ConceptE; { i / i } noexcept -> ConceptE; { i % i } noexcept -> ConceptE; { i *= i } noexcept -> ConceptE; { i /= i } noexcept -> ConceptE; { i %= i } noexcept -> ConceptE; { i | i } noexcept -> ConceptE; { i & i } noexcept -> ConceptE; { i |= i } noexcept -> ConceptE; { i &= i } noexcept -> ConceptE; { ~i } noexcept -> ConceptE; { i ^ i } noexcept -> ConceptE; { i << x } noexcept -> ConceptE; { i >> x } noexcept -> ConceptE; { i ^= i } noexcept -> ConceptE; { i <<= x } noexcept -> ConceptE; { i >>= x } noexcept -> ConceptE; }; template<typename T> concept ConceptG = requires(T i, int x) { { i++ } noexcept -> ConceptF; { ++i } noexcept -> ConceptF; { i-- } noexcept -> ConceptF; { --i } noexcept -> ConceptF; { i + i } noexcept -> ConceptF; { i - i } noexcept -> ConceptF; { i += i } noexcept -> ConceptF; { i -= i } noexcept -> ConceptF; { i * i } noexcept -> ConceptF; { i / i } noexcept -> ConceptF; { i % i } noexcept -> ConceptF; { i *= i } noexcept -> ConceptF; { i /= i } noexcept -> ConceptF; { i %= i } noexcept -> ConceptF; { i | i } noexcept -> ConceptF; { i & i } noexcept -> ConceptF; { i |= i } noexcept -> ConceptF; { i &= i } noexcept -> ConceptF; { ~i } noexcept -> ConceptF; { i ^ i } noexcept -> ConceptF; { i << x } noexcept -> ConceptF; { i >> x } noexcept -> ConceptF; { i ^= i } noexcept -> ConceptF; { i <<= x } noexcept -> ConceptF; { i >>= x } noexcept -> ConceptF; }; template<typename T> concept TestedConcept = ConceptA<T> && ConceptB<T> && ConceptC<T> && ConceptD<T> && ConceptE<T> && ConceptF<T> && ConceptG<T>; void foo(TestedConcept auto const &) {} void foo(auto const &) {} void later() { int i { 0 }; int * ip = &i; foo(i); foo(ip); }
=========== Источник: habr.com =========== Похожие новости:
Блог компании Orion Innovation ), #_vysokaja_proizvoditelnost ( Высокая производительность ), #_programmirovanie ( Программирование ), #_c++ |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:18
Часовой пояс: UTC + 5