1

Generally speaking, unless explicitly allowed, the behavior of a C++ program that tries to take the pointer of a standard library function is unspecified. Which means extra caution should be taken before passing them as Callable. Instead it is typically better to wrap them in a lambda.

More on the topic: Can I take the address of a function defined in standard library?


However, C++20 introduced Constrained algorithms, or ranged algorithms, based on the Range-v3 library; where function-like entities, such as std::ranges::sort and std::ranges::transform, are introduced as Niebloids.

While the original library has created a functor class for each functions in the algorithm library, and each niebloids, such as ranges::sort, is simply a named object of the corresponding functor class; the standard does not specify how they should be implemented.


So the question is if the behavior of passing a Niebloid as a Callable, such as std::invoke(std::ranges::sort, my_vec), specified/explicitly allowed?

Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39

1 Answers1

1

All the spec says, in [algorithms.requirements] is:

The entities defined in the std​::​ranges namespace in this Clause are not found by argument-dependent name lookup ([basic.lookup.argdep]). When found by unqualified ([basic.lookup.unqual]) name lookup for the postfix-expression in a function call ([expr.call]), they inhibit argument-dependent name lookup.

The only way to implement that, today, is by making them objects. However, we don't specify any further behavior of those objects.

So this:

std::invoke(std::ranges::sort, my_vec)

will work, simply because that will simply evaluate as std::ranges::sort(my_vec) after taking a reference to it, and there's no way to really prevent that from working.

But other uses might not. For instance, std::views::transform(r, std::ranges::distance) is not specified to work, because we don't say whether std::ranges::distance is copyable or not - std::ranges::size is a customization point object, and thus copyable, but std::ranges::distance is just an algorithm.

The MSVC implementation tries to adhere aggressively to the limited specification, and its implementation of std::ranges::distance is not copyable. libstdc++, on the other hand, just makes them empty objects, so views::transform(ranges::distance) just works by way of being not actively rejected.

All of which to say is: once you get away from directly writing std::ranges::meow(r) (or otherwise writing meow(r) after a using or using namespace), you're kind of on your own.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thank you for the quick answer! I just want to make sure, *'So this: [...] will work, simply because [...] there's no way to really prevent that from working'*. Is that based on the presumption that *'The only way to implement that, today, is by making them objects'*? While I don't think any particular implementation is doing that, it is mentioned on cppreference that it might be implemented with special compiler extensions. – Ranoiaetep Jan 24 '23 at 21:08
  • 1
    @Ranoiaetep Sure, I suppose if somebody's going to go implement a dedicated language feature to implement `std::ranges::` algorithms, that dedicated language feature can do... whatever is they want it to do, so hypothetically it could also reject the `std::invoke(meow, r)` usage. But, short of that, no within-standard-C++23 implementation can reject such a thing. – Barry Jan 24 '23 at 22:06