std :: regular_invocable и аргументы по значению

Предполагается ли, что следующий пример демонстрирует нарушение предусловия?

#include <memory>
#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> x{1, 2, 3, 4};

    auto r = x | std::views::transform([](int x){return std::make_unique<int>(x); });

    auto r2 = r | std::views::transform([](std::unique_ptr<int> v){
                   return *v;
               });
    for(auto i : r2) {
        std::cout << i << ' ';
    }
}

std::ranges::transform_view имеет ограничение на F fun, которое должно быть regular_invocable<F&, range_reference_t<V>>. Как написано в [concept.regularinvocable] regular_invocable не должен изменять объект функции или аргументы. Таким образом, функция r2 нарушает это семантическое ограничение, поскольку она изменяет аргумент, уходя от него.

Верна ли эта интерпретация?


person Serikov    schedule 06.05.2021    source источник


Ответы (1)


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

Однако обе функции ошибочны по разным причинам. Первая лямбда нарушает предварительное условие быть с сохранением равенства (что означает, что для того же аргументы, вы получите то же возвращаемое значение).

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

person Nicol Bolas    schedule 06.05.2021
comment
r - это диапазон prvalue unique_ptr, поэтому компилируется нормально. Копировать не нужно - person Barry; 06.05.2021
comment
Первая лямбда не нарушает сохранения равенства. Сохранение равенства заключается не в возврате одного и того же объекта и не на основе operator==. Для целей этой программы возвращаемое значение первой лямбды достаточно одинаково при многократном вызове с одинаковыми аргументами. - person Serikov; 06.05.2021
comment
@Serikov: Для целей этой программы Стандарт на самом деле не заботится о том, что, по вашему мнению, означает равенство. Если результаты вызова одного и того же выражения для одних и тех же значений не приводят к равному значению, то это не сохранение равенства. Вы не можете произвольно определять, что означает равенство. - person Nicol Bolas; 06.05.2021
comment
не приводят к равному значению, тогда как следует проверить это равенство? А как насчет типов без operator==? - person Serikov; 06.05.2021
comment
@NicolBolas Есть вопрос о сохранении равенства здесь. Его принятый ответ неверен? - person Serikov; 06.05.2021
comment
@Serikov: Думаю, вы неправильно понимаете, о чем говорится в этом посте. Когда он предлагает равенство без учета регистра, это будет включать сценарий, в котором проверка на равенство фактически нечувствительна к регистру. Пользователь говорит: «Предоставляемые мной операции должны отражать эту семантику». То есть, например, он не будет использовать std::string::operator==. Вы должны указать operator== для типа, нечувствительного к регистру. unique_ptr дает определение равенства, и ваш код не отменяет его. Следовательно, то, что говорит unique_ptr, остается в силе. - person Nicol Bolas; 06.05.2021
comment
@Serikov: То есть у вас может быть тип указателя с уникальным владельцем только для перемещения, который определяет равенство, реализуя operator== через равенство значений, а не равенство указателей. Хорошо. Однако этот тип не может быть unique_ptr, поскольку он уже определяет равенство по указателю, а не по значению. Вы не можете выбрать для типа, как он определяет равенство (если только он не перегрузил operator==). - person Nicol Bolas; 06.05.2021
comment
Уточните, пожалуйста, как можно такое равенство проверять для типа без operator==. - person Serikov; 06.05.2021
comment
@Serikov: Если тип еще не имеет перегрузки operator==, вы можете создать ее в его пространстве имен (за исключением стандартного пространства имен, в которое вы не можете добавить). Если он уже есть или был удален, вы не можете этого сделать. - person Nicol Bolas; 06.05.2021
comment
Нет, сохранение равенства - это семантическое ограничение, а не фактическое равенство, основанное на operator==. В любом случае, считаете ли вы, что запрет на перемещение из одинаковых диапазонов во время трансформации был намеренным? Вроде при трансформации диапазона ‹int› - ›range ‹string› -› range ‹object› запрещено двигаться со строк :( - person Serikov; 06.05.2021
comment
@Serikov: Да, transform не предназначен для операции модификации. В этом весь смысл требования сохранения равенства. Нет, сохранение равенства - это семантическое ограничение, а не фактическое равенство на основе оператора ==. Это неверно. Семантическое равенство типа - это понятие, которое существует; operator== требуется для правильной реализации этой концепции. То есть unique_ptr определяет равенство по указателю; следовательно, его operator== сравнивает значения указателей, а не значения объектов. Семантика выражается через оператор. - person Nicol Bolas; 06.05.2021
comment
На самом деле сохранение равенства - это не то же самое, что не изменять. Такое изменение запрещает не сохранение равенства, а дополнительное ограничение на regular_invocable. - person Serikov; 06.05.2021