runtime — Что такое C ++ Runtime Concepts?

В последнее время я искал в Интернете подробности о концепциях C ++ и нашел несколько ссылок на то, что в нескольких статьях называется «концепции времени выполнения.«Как именно они отличаются от концепций времени компиляции, почему они были представлены в первую очередь, как они будут реализованы и почему они важны для будущего C ++? Взглянув на документы, я получил общее представление о том, что концепции времени выполнения призваны смягчить текущую напряженность, которая в настоящее время существует между объектно-ориентированным и универсальным кодом, но я больше ничего не получаю от них.

5

Решение

Это мое понимание того, что происходит. Это начинается с другого ракурса: стирание типа.

std::function<void()> пример класса стирания типа Он берет понятия «вызов без аргументов и ничего не возвращает» вместе с вспомогательными понятиями «копировать конструкцию» и «уничтожить» и заключает его в аккуратный небольшой пакет.

Так что вы можете сделать

void groot () { std::cout << "I am groot!\n"; }
std::function<void()> f = groot;
f();

а также groot вызывается. Или мы можем передать лямбда, или функциональный объект, или std::bind выражение или boost::function к std::function и вызвать его.

Все эти типы могут быть скопированы, уничтожены и вызваны: так std::function может потреблять их и создавать единый интерфейс во время выполнения. Кроме операций, которые они поддерживают, типы, которые std::function может хранить и выполнять не связаны. Не существует иерархии классов, которая связывает функцию groot к лямбде или к boost::function,

Конструктор std::function<void()> это берет вещи, которые не std::functions type-стирает свой аргумент в соответствии с понятиями копировать, уничтожать и вызывать с подписью void(),

Начнем с этого:

template<class Sig>
struct func_type_eraser;

template<class R, class... Args>
struct func_type_eraser<R(Args...)> {
// invoke:
virtual R operator()(Args...) const = 0;
// copy:
virtual func_type_eraser* clone() const = 0;
// destroy:
virtual ~func_type_eraser() {};
};
template<class Sig, class T>
struct func_type_eraser_impl; // TODO!

Здесь мы имеем 3 концепции копирования, уничтожения и вызова, каждое из которых представлено как чисто виртуальная функция.

template<class Sig>
struct function;
template<class R, class... Args>
struct function<R(Args...)> {
std::unique_ptr<func_type_eraser<R(Args...)>> pImpl;
// invoke:
R operator()( Args... args ) const {
return (*pImpl)( std::forward<Args>(args)... );
}
// destroy:
~function() = default;
// copy:
function(function const& o) : pImpl( o.pImpl ? o.pImpl->clone() : nullptr ) {}
// move:
function(function&&) = default;
// TODO: operator=

// technical issues, ignore:
function(function& o) : function(const_cast<function const&>(o)) {}
function(function const&& o) : function(o) {}

// type erase:
template<class T>
function(T&& t) : pImpl( new func_type_eraser_impl<R(Args...), std::decay_t<T>>{std::forward<T>(t)} )
{}
};

Здесь мы включаем концепции, которые мы хотим поддержать, в то, что известно как Regular тип — тип значения типа. У нас есть базовый указатель и виртуальная иерархия (небольшая, пока невидимая), но тип function выглядит так же, как int — Вы можете скопировать, назначить и т. д.

Каждое из понятий — вызывать, копировать, перемещать, уничтожать — пересылается pImpl (Кроме move, который мы можем эффективно реализовать на этом уровне).

Только половина работы по стиранию типа выполняется здесь. Эта часть позволяет нам назначить что-либо на наш function экземпляры классов. Мы можем сделать немного лучше, протестировав T передает требования концепции — что она может быть скопирована, уничтожена и вызвана с требуемой подписью — прежде чем допустить ее нашему конструктору. (Текущий C ++ std::function не в состоянии сделать это, к большому раздражению).

Последняя часть стирания типа:

template<class R, class... Args, class T>
struct func_type_eraser_impl<R(Args...), T> : func_type_eraser<R(Args...)> {
// type erase storage:
T t;
// invoke:
virtual R operator()(Args... args) const override {
return t( std::forward<Args>(args)... );
}
// copy:
virtual func_type_eraser_impl* clone() const override {
return new func_type_eraser_impl{t};
}
// destroy:
virtual ~func_type_eraser_impl() {}
};

… Где мы реализуем концепцию интерфейсов, представленных в func_type_eraser для определенного типа T,

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

Мы можем пойти еще дальше:

Мы можем даже поддерживать все, что клиент может предоставить функции для поддержки этих концепций.

Самый простой способ сделать это — вызвать бесплатную функцию, такую ​​как std::beginв контексте, который допускает ADL (поиск, зависящий от аргумента).

У нас есть реализация стирания типов, которая вместо непосредственного взаимодействия с объектом вместо этого вызывает свободную функцию в контексте ADL.

Обеспечить реализацию по умолчанию этой функции, которая делает что-либо от «сбоев» до «проверок метода» .begin() и вызывает его «или» делает неэффективную версию «или» проверяет свойства переданного типа и определяет разумный способ выполнить задачу «.

С помощью этой техники мы можем позволить клиентам расширять стирание типов и использовать более широкие концепции.

В качестве конкретного примера представьте, что у нас была концепция для печати. Что-то можно распечатать, если оно имеет ostream << X перегружен, или если он имеет print(X) перегружен.

Мы добавляем print_it к нашему типу интерфейса стирания. Это using impl_namespace::printзатем print(t),

impl_namespace::print(X) просто делает cout << X,

Это все отделено. Вы можете взять тип, который кто-то другой написал без понятия печати, добавить концепцию печати через свободную функцию в свое пространство имен, а затем передать его в нашу систему стирания типов, и система стирания типов подключит его.

Увидеть этот канал 9 видео Например, кто-то использует похожие методы для создания игрушечного документа с бесконечной отменой и отображением, которое может быть расширено до произвольного числа типов, включая встроенные типы.

Теперь представьте языковую поддержку для этого. Будучи в состоянии описать набор понятий, которые вы хотите набрать, стереть, и сказать «создать регулярный тип, который стирает эти типы».

Если у вас есть алгоритм, который поддерживается упомянутыми другими концепциями, вы можете сказать «поддержка стирания типа для этого алгоритма». Любые клиенты, которые знают о стирании типа алгоритма и имеют лучшую поддержку для него, могут автоматически добавить созданный пользователем в ваш интерфейс. Те, кто не может использовать концепции стертых типов, которые вы предоставили для его реализации.

В момент удаления типа, когда ваши концепции переносятся из понятых во время компиляции в виртуальную и во время выполнения, поддержка удаления типа для вашего алгоритма может быть очень эффективной, даже если поддержка концепций в вашем типе была concept-map на основе (т. е. пользовательских функций, где они предусмотрены для решения проблем. Ваш тип не является наивно копируемым, но есть функция клона, которая копирует его в подходящее хранилище, скажем). Стирание типа концепции алгоритма может учитывать полное отображение концепции времени компиляции вместо виртуального отображения концепции времени выполнения, обеспечивая прирост производительности, даже если нет принципиально более быстрого алгоритма.

Если это сделано с особой тщательностью, вы можете взять объект стирания типа с меньшим количеством понятий и расширить его до объекта с большим количеством понятий, если новые понятия поддерживаются меньшим количеством понятий. Клиенты, которые «не знали», что вам нужен быстрый бинарный поиск (скажем), в конечном итоге поддержали бы его через интерфейс времени выполнения: те, кто это сделал, предоставили бы вам быстрый бинарный поиск, настроенный для вашего типа.

Сделав еще один шаг, вы можете получить дополнительную поддержку концепции в вашем классе стирания типов. Например, итератор со стертым типом может дополнительно поддерживать итерацию с произвольным доступом. Алгоритмы, которые принимают итераторы, могут проверять итерацию с произвольным доступом и, если это так, создавать лучшую реализацию. Концепция двоичного поиска в диапазоне может проверять, имеет ли диапазон поддержку концепции двоичного поиска, а если нет, то имеет ли он поддержку произвольного доступа, и, если это невозможно, использовать версию бинарного поиска (O (n) foward итератор, O (lg ( н)) сравнения). В каждом случае он может использовать «более специализированную» реализацию.

Все это аналогично тому, как концепции работают во время компиляции. За исключением того, что это происходит во время выполнения, и имеет эту дополнительную систему стирания типа.

7

Другие решения


По вопросам рекламы ammmcru@yandex.ru
Adblock
detector