Ответ 1
Что такое объекты точек настройки?
Это экземпляры функциональных объектов в пространстве имен std
которые выполняют две цели: сначала безоговорочно инициируют (заданные) требования к типу аргумента (ов), затем отправляют правильную функцию в пространстве имен std
или через ADL.
В частности, почему они объекты?
Это необходимо, чтобы обойти вторую фазу поиска, которая напрямую привела бы к предоставленной пользователем функции через ADL (это должно быть отложено проектом). Подробности смотрите ниже.
... и как их использовать?
При разработке приложения: вы в основном не делаете. Это стандартная функция библиотеки, она добавит проверку концепции к будущим точкам настройки, что, как мы надеемся, приведет к появлению четких сообщений об ошибках, когда вы запутаетесь в экземплярах шаблона. Однако при квалифицированном вызове такой точки настройки вы можете напрямую использовать ее. Вот пример с воображаемым объектом std::customization_point
который придерживается дизайна:
namespace a {
struct A {};
// Knows what to do with the argument, but doesn't check type requirements:
void customization_point(const A&);
}
// Does concept checking, then calls a::customization_point via ADL:
std::customization_point(a::A{});
В настоящее время это невозможно, например, с помощью std::swap
, std::begin
и т.п.
Пояснение (краткое изложение N4381)
Позвольте мне попытаться переварить предложение за этим разделом в стандарте. Есть две проблемы с "классическими" точками настройки, используемыми стандартной библиотекой.
-
Они легко ошибаются. Например, обмен объектами в общем коде должен выглядеть следующим образом
template<class T> void f(T& t1, T& t2) { using std::swap; swap(t1, t2); }
но сделать квалифицированный вызов
std::swap(t1, t2)
вместо этого слишком просто - пользовательскийswap
никогда не будет вызван (см. N4381, Мотивация и область действия) -
Точнее, нет способа централизовать (концептуализировать) ограничения на типы, передаваемые таким пользовательским функциям (именно поэтому эта тема приобрела важность в С++ 20). Снова из N4381:
Предположим, что будущая версия
std::begin
требует, чтобы ее аргумент моделировал концепцию Range. Добавление такого ограничения не повлияет на код, который используетstd::begin
идиоматически:using std::begin;
begin(a);
Если вызов begin отправляет пользовательскую перегрузку, то ограничение наstd::begin
было обойдено.
Решение, которое описано в предложении, смягчает обе проблемы с помощью подхода, подобного следующему, воображаемой реализации std::begin
.
namespace std {
namespace __detail {
/* Classical definitions of function templates "begin" for
raw arrays and ranges... */
struct __begin_fn {
/* Call operator template that performs concept checking and
* invokes begin(arg). This is the heart of the technique.
* Everyting from above is already in the __detail scope, but
* ADL is triggered, too. */
};
}
/* Thanks to @cpplearner for pointing out that the global
function object will be an inline variable: */
inline constexpr __detail::__begin_fn begin{};
}
Во-первых, квалифицированный вызов, например, std::begin(someObject)
всегда std::begin(someObject)
через std::__detail::__begin_fn
, что желательно. Что происходит с неквалифицированным звонком, я снова ссылаюсь на оригинальную статью:
В случае, если начало вызывается неквалифицированным после
std::begin
в область действияstd::begin
, ситуация другая. На первом этапе поиска имя begin будет преобразовано в глобальный объектstd::begin
. Так как поиск нашел объект, а не функцию, вторая фаза поиска не выполняется. Другими словами, еслиstd::begin
является объектом, тоusing std::begin; begin(a);
using std::begin; begin(a);
эквивалентноstd::begin(a);
который, как мы уже видели, выполняет аргумент-зависимый поиск от имени пользователя.
Таким образом, проверка концепции может быть выполнена внутри объекта функции в пространстве имен std
, прежде чем будет выполнен вызов ADL для функции, предоставленной пользователем. Нет способа обойти это.