0

Is it intended that following example exhibit precondition violation?

#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 has constraint on F fun that it should be regular_invocable<F&, range_reference_t<V>>. As written in [concept.regularinvocable] regular_invocable "shall not modify the function object or the arguments". So r2 function violates that semantic constraint as it modifies argument by moving from it.

Is this interpretation valid?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Serikov
  • 1,159
  • 8
  • 15

1 Answers1

0

Remember: arguments are what you pass in; parameters are what the function sees. The first function cannot modify the arguments because it takes its parameter by value. This means the parameter is a copy of the argument.

However, both functions are wrong, for different reasons. The first lambda violates the precondition of being "equality preserving" (meaning that, for the same arguments, you get the same return value).

The second one shouldn't even compile because its parameter is taken by value. unique_ptrs are move-only, and the view should not be able to move from the contents of the iterator unless it were a moveable range. And if it did compile, it would violate the precondition you cited, since the argument is being modified as it becomes a parameter (move is a modifying operation).

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • `r` is a range of prvalue `unique_ptr`, so it compiles fine. No copy needs to happen – Barry May 06 '21 at 13:47
  • First lambda does not violate "equality preserving". "Equality preserving" is not about "returning same object" and not based on `operator==`. For the purpose of this program return value of first lambda is "equal enough" when being called multiple times with equal arguments. – Serikov May 06 '21 at 16:24
  • @Serikov: "*For the purpose of this program*" The standard doesn't really care about what you think "equality" means. If the results of invoking the same expression on the same values do not result in an equal value, then it's not equality preserving. You don't get to arbitrarily define what "equal" means. – Nicol Bolas May 06 '21 at 16:30
  • "do not result in an equal value," Then how should this equality be tested? What about types without `operator==`? – Serikov May 06 '21 at 16:55
  • @NicolBolas There exist question about equality preservation [here](https://stackoverflow.com/a/57709410/4899740). Is its accepted answer wrong? – Serikov May 06 '21 at 17:06
  • @Serikov: I think you're mis-understanding what that post is saying. When it suggests a case-insensitive equality, this would involve a scenario where the equality test actually is case-insensitive. The user says, "The operations I supply must reflect this semantics." That is, it wouldn't be using `std::string::operator==`, for example. You would provide an `operator==` for the type that is case-insensitive. `unique_ptr` provides a definition of equality, and your *code* doesn't override it. Therefore, what `unique_ptr` says stands. – Nicol Bolas May 06 '21 at 17:23
  • @Serikov: That is, you can have a move-only uniquely owning pointer type that defines equality by implementing `operator==` through value-equality rather than pointer-equality. That's fine. However, that type *cannot be* `unique_ptr`, since it already defines equality by pointer rather than by value. You cannot choose for a type how it defines equality (unless it didn't overload `operator==`). – Nicol Bolas May 06 '21 at 17:27
  • Please clarify how can such an equality tested for the type without `operator==`. – Serikov May 06 '21 at 18:27
  • @Serikov: If a type doesn't already have an `operator==` overload, you can create one in its namespace (except for the standard namespace, which you cannot add to). If it already has one, or has deleted one, you cannot. – Nicol Bolas May 06 '21 at 18:36
  • No, "equality preserving" is a semantic constraint not an actual `operator==` based equality. Either way, do you think that forbidding moving from similar ranges during transformation was intentional? Like when transforming range -> range -> range it is forbidden to move from strings :( – Serikov May 06 '21 at 18:49
  • @Serikov: Yes, `transform` is *not* intended to be a modifying operation. That's the *whole point* of requiring it to be "equality preserving". "*No, "equality preserving" is a semantic constraint not an actual operator== based equality.*" That's incorrect. The semantic equality of a type is a concept that exists; `operator==` is required to implement that concept correctly. That is, `unique_ptr` defines equality by pointer; therefore, its `operator==` compares pointer values, not object values. The way the semantic is expressed is *through* the operator. – Nicol Bolas May 06 '21 at 18:55
  • Actually equality preserving is not the same as "not modifying". What forbids that modification is not equality preservation but additional constraint on `regular_invocable`. – Serikov May 06 '21 at 19:03