adaptor(args...)(r) is not equivalent to std::bind_back(adaptor, args...)(r)
Hewill Kang

Created on 2023-10-11.00:00:00 last changed 3 weeks ago


Date: 2023-11-15.00:00:00

[ 2023-11-02; Reflector poll ]

Set priority to 4 after reflector poll.

Date: 2023-10-11.00:00:00

[range.adaptor.object] p8 specifies that:

The expression adaptor(args...) produces a range adaptor closure object f that is a perfect forwarding call wrapper ([func.require]) with the following properties:

According to the subsequent description, it can be inferred that the behavior is similar to std::bind_back(adaptor, args...) which also returns a perfect forwarding call wrapper.

Among them, "A perfect forwarding call wrapper is an argument forwarding call wrapper that forwards its state entities to the underlying call expression" according to [func.require]/4, and call wrapper in [func.require]/3 is described as:

Every call wrapper ([func.def]) meets the Cpp17MoveConstructible and Cpp17Destructible requirements.

In order to conform with the specification, standard functions that return perfect forwarding call wrappers such as std::bind_front/back and std::not_fn all Mandates that (is_constructible_v<BoundArgs, Args> && ...) and (is_move_constructible_v<BoundArgs> && ...) are each true, the former condition corresponds to [range.adaptor.object] p8:

The expression adaptor(args...) is well-formed if and only if the initialization of the bound argument entities of the result, as specified above, are all well-formed.

However, the latter does not have a corresponding description in <ranges>. In other words, range adaptor objects do not explicitly indicate that the bound argument must be move-constructible. This results in implementation divergence for some uncommon types (demo):

#include <ranges>
#include <string_view>

constexpr struct WeirdFive {
  WeirdFive() = default;
  WeirdFive(const WeirdFive&) = default;
  constexpr operator int() const { return 5; }

  WeirdFive(WeirdFive&&) = delete;
} five;

constexpr std::string_view sv{"hello"};
static_assert(sv == std::views::take(five)(sv)); // libstdc++/libc++ reject, MSVC-STL accepts

Above, libstdc++ always moves arguments into internal members, which leads to hard errors in the member initializer list; libc++ uses std::bind_back for argument binding, which also leads to hard errors in the function body as the former requires arguments to be move-constructible; MSVC-STL is the most compliant with current wording.

Date User Action Args
2023-11-03 18:08:28adminsetmessages: + msg13808
2023-10-11 00:00:00admincreate