Проблема в том, что возвращаемого объекта достаточно для проверки, находится ли он в конце (я оборачиваю API).
C ++ 20 распознает это как "Iterator / Диапазон Sentinel, который заменил традиционную модель «Итератор / Итератор», которая требует, чтобы два Итератора были одного типа. C ++ 17 был на самом деле преимущественно расширен для поддержки такой конструкции, как C ++ 14 и раньше требовал, чтобы итераторы begin
и end
основанного на диапазоне типа диапазона for
l oop быть того же типа. C ++ 17 изменил это, чтобы учесть разные типы.
Sentinel - это тип, который действует как конечный итератор, не будучи итератором. Действительно, единственное реальное требование к Sentinel - это то, что он проверяется на равенство с итератором, с которым он связан. Любой итератор, который проверяет равенство своего стража, считается концом диапазона.
Очевидно, что другим итератором является Страж. Но тот факт, что Страж не должен быть Итератором, расширяет возможности.
Если у вас есть значение Стража, которое не является Итератором, вероятно, что Страж ничего не делает. В большинстве таких случаев итератор хранит всю информацию, необходимую для определения того, находится ли она в конце. istream_iterator
и подобные потоковые итераторы являются примерами такого рода, и они могут иметь Sentinels (они не имеют, поскольку они предшествуют парадигме Sentinel. Вместо этого «конечный» итератор для такого диапазона является построенным по умолчанию итератором) , Таким образом, ваш operator==
- это просто способ вызова Iterator.IsEnd()
или любой другой функции, которая вам нужна.
Какой тип я могу использовать, чтобы подсказать компилятору вообще пропустить аргумент?
Нет. Но это нормально, потому что вам не нужно.
Давайте посмотрим, где будут существовать наши значения Стража. В большинстве случаев, когда Страж является просто средством для вызова operator==
, Страж является моностатом. Это отдельный тип (который важен , потому что мы не хотим, чтобы тип Sentinel istream можно было использовать с диапазоном подсчета или какой-то другой ерундой), но если он имеет функцию-член, это просто operator==
.
Это означает, что тип диапазона не должен хранить Sentinel; просто нужно, чтобы end
произвел один. Таким образом, он возвращает значение только что созданного Sentinel.
Поскольку (завершенные) объекты в C ++ должны занимать память, он будет иметь размер от 1 до 4 байтов. В худшем случае в стеке находится 4-байтовый объект.
Далее range- for
вызовет operator==
в паре Iterator / Sentinel. Ну, в принципе, нет оправдания тому, что эта функция не была сделана inline
, и, поскольку все, что эта функция будет делать, это вызывать функцию в Итераторе, у компилятора (с включенной оптимизацией) нет оправдания, чтобы не встроить функцию.
И как только он встроен ... компилятор видит, что Страж ничего не делает. Поскольку функция встроенная, она не будет передана в качестве параметра. И это на самом деле не используется встроенным кодом; он просто сидит там.
В худшем случае, компилятор оставит в стеке 4-байтовый объект, который ничего не делает. Но, скорее всего, компилятор просто избавится от него.
Но если вы не хотите поверить мне на слово, я представляю вам nul_terminator
, Sentinel для const char*
итераторов, которые указывают на NUL-концевые строки. Естественно, operator==
разыменовывает итератор и проверяет его на 0.
struct nul_terminator {};
//This code is C++17, since C++20 support is spotty, but you only need one of these in C++20.
bool operator==(const char *it, nul_terminator) {return *it == 0;}
bool operator==(nul_terminator, const char *it) {return *it == 0;}
bool operator!=(const char *it, nul_terminator) {return *it != 0;}
bool operator!=(nul_terminator, const char *it) {return *it != 0;}
class string_range
{
public:
const char *begin() {return str_;}
nul_terminator end() {return {};}
string_range(const char *str) : str_(str) {}
private:
const char *str_;
};
int manual_sum_values_of_string(const char *str)
{
int accum = 0; //Just to give the function something to do, so the optimizer doesn't make it go away.
for(; *str != 0; ++str)
accum += *str;
return accum;
}
int range_sum_values_of_string(string_range str)
{
int accum = 0; //Just to give the function something to do, so the optimizer doesn't make it go away.
for(auto ch : str)
accum += ch;
return accum;
}
Если вы посмотрите на выходные данные сборки из трех основных компиляторов для manual_sum_values_of_string
против range_sum_values_of_string
вы обнаружите, что они практически идентичны. И я не имею в виду «есть накладные расходы, но вы можете игнорировать это». Я имею в виду «то же самое, за исключением того, что порядок пары независимых кодов операций поменяется местами».
G CC дает эквивалентные результаты на уровне оптимизации 1, в то время как Clang и MSV C требуют O2, прежде чем они получить равные результаты.
В принципе: не беспокойтесь об этом. Компиляторы умны, так что пусть они делают свою работу.