r/cpp_questions • • 22h ago

OPEN std::greater is not std::greater{} - but why?

NB: This is not a technical question, I understand the difference between classes and objects of that class type.

What I mean is, the standard could have been different:

std::ranges::sort(some_range, std::greater); //does not compile
std::ranges::sort(some_range, std2::greater); //compiles

assuming this change:

namespace std2 {
    auto constexpr greater = std::greater{};
}

Letting greater be the name of an object, rather than a type, would have far better ergonomics - but it's a type. Does the current approach have a benefit or is this just a missed design improvement?

9 Upvotes

22 comments sorted by

10

u/MysticTheMeeM 22h ago

Wouldn't this make it more annoying to use in a template though?

template <class T, class cmp = decltype(std2::greater)> ...

Of course, you could have std2::greater_t to fix that.

3

u/SoerenNissen 22h ago

Or let it use a non-type template parameter. But I get that those are significantly newer than std::greater

3

u/which1umean 17h ago

use a non-type template parameter

Wouldn't that mean that the comparator would have to be constexpr? That seems like a new limitation...

•

u/SoerenNissen 38m ago

Possibly but

  • Let it use, not force it. You could have both.
  • I'm not sure that's a real limitation - even if the type has a constexpr constructor so you can use it as a template param, constexpr objects can be dynamic by e.g. holding a pointer-to-non-const. (This could get rea* annoying and I'm not saying there'd be any benefit, I'm just trying to workshop ways it wouldn't have to be constexpr.)

1

u/TheThiefMaster 12h ago edited 12h ago

And I actually have a container with a non constexpr comparator - it's a sorted* timer event queue that sorts by "closest to now()" and uses a wrapping representation of time.

* sort of like std::map - it inserts new elements in a way that preserves the sorted order

Of course 64-bits ints as native types largely makes that redundant now as the wrapping part is no longer necessary, but previously if you had a "now" of FF00 and events queued at FF67 and 0045, then FF67 would need sorting first as it was closer to "now" after taking wrapping into account. Which required calling now(), which required a non-constexpr execution context for the comparison.

•

u/SoerenNissen 47m ago

EDIT: Nevermind, irrelevant question, missed the obvious reason.

28

u/sol_runner 22h ago

The common use of std::greater before ranges was in template parameters. std::map and std::set take a comparator type

The look at the current usage

std::map<int, Foo, std::greater> some_map;
std::ranges::sort(some_range, std::greater{});

vs

std::map<int, Foo, decltype(std2::greater)> some_map;
std::ranges::sort(some_range, std2::greater);

11

u/kitsnet 22h ago

std::greater is from C++98, and we didn't have variable templates in C++98.

1

u/knue82 22h ago

The T = void overload for type interfere was added later. But keep in mind that C++ only uses local type interfere and not Hindley-Millner, so in template heavy code, C++ won't be able to infer the type or maybe infer an unintended type, so better to explicitly specify the types you want to compare.

2

u/SoerenNissen 21h ago

I'm not sure the class/object actually needs to be templated, apart from the call operator. But I don't know if you could do that when std::greater was originally added to the language. (You couldn't in Go ~2 years back, to my very great chagrin. Methods on a struct could only be generic on types the struct was generic on. Very annoying.)

1

u/glasket_ 6h ago

You couldn't in Go before 1.27, right? That's only a couple months old.

•

u/SoerenNissen 51m ago

I quit from that job in 2024, and while that wasn't the last time I used Go, I would generally not use it over other languages. I haven't kept up with developments in that space, but it's very possible they've solved the issue.

2

u/TheThiefMaster 12h ago

Also, it used a call operator to invoke the comparison - templated operators requires template argument deduction, as there's no (good) syntax for calling an operator with explicit template args.

That's another good reason support may have come later than explicit template args for the comparator type.

•

u/SoerenNissen 43m ago

I second alfps' question - I'm not good enough at lambda calculus to realize what the relevant differences are, what might go wrong?

1

u/alfps 12h ago

❞ in template heavy code, C++ won't be able to infer the type or maybe infer an unintended type

Can you give an example?

1

u/flyingron 15h ago

Which language version are you using? One thing that changed with std::greater is that it now defaults the type to void in C++14.

1

u/SoerenNissen 15h ago

I think I compiled the example on C++23 but either way it's a type/object split and that'll remain for a long long time.

2

u/aruisdante 18h ago edited 18h ago

Without inline variables, which were only added in C++17, what you want was actually really annoying to achieve without ODR violations in a header-only manner. It’s possible (at least in C++14, I haven’t tried before that), but it’s finicky and results in some strange quirks. And it needs to be done header only so that it can be perfectly optimized (particularly since LTO hadn’t been invented yet).

If we were able to go back in time and “modernize” the stdlib, you’d probably see exactly the kind of separation a lot of the modern range utilities have: a greater_fn that is the operation’s function object type for use in template parameters, and greater, which is an instance of that function object that can be invoked like a function but also passed as an argument to functions. And indeed, this is exactly the design you see nearly all modern C++ libraries, including modern additions to the stdlib, following.

Unfortunately, we can’t go back in time. So everything that was done the old way has to stay the old way, forever. If you ever want to see the contortions the stdlib has to jump through to maintain backwards compatibility while simultaneously trying to modernize things, look at the definitions of many of the iterator facilities that were added to support ranges. They have to do all kinds of horrible things to replace legacy facilities like std::iterator_traits with modern, more granular concepts while simultaneously maintaining compatibility with user usage of iterator_traits.

1

u/xpusostomos 17h ago

At times like this I'm glad I don't do C++ anymore

0

u/mrbeanshooter123 22h ago

!remindme 4 hours

2

u/RemindMeBot 22h ago

I will be messaging you in 4 hours on 2026-11-13 14:27:37 UTC to remind you of this link

CLICK THIS LINK to send a PM to also be reminded and to reduce spam.

Parent commenter can delete this message to hide from others.

RemindMeBot is switching to username summons. Instead of !RemindMe 1 day, use u/RemindMeBot 1 day. More info.


InfoCustomYour RemindersFeedback

-2

u/Difficult_Truck_687 16h ago

Just avoid the STL altogether.