August 29th, 2018

Q&A: How to specialize std::sort by binding the comparison function

This post is part of a regular series of posts where the C++ product team here at Microsoft and other guests answer questions we have received from customers. The questions can be about anything C++ related: Visual C++, the standard language and library, the C++ standards committee, isocpp.org, CppCon, etc. Today’s Q&A is by Herb Sutter.

Question

A reader recently asked: I am trying to specialize std::sort by binding the comparison function. I first tried:

auto sort_down = bind(sort<>,_1,_2,[](int x, int y){return x > y;});

It couldn’t infer the parameter types. So then I tried:

auto sort_down = bind(sort<vector<int>::iterator,function<int(int)>>,
                      _1,_2,[](int x, int y){return x > y;});

Is there a straightforward way to do this? Another example:

auto f = bind(plus<>(), _1, 1)

Here bind has no trouble deducing the template arguments in this case, but when I use a function template for the original callable, it’s not so happy. Just wanting to be consistent with this usage.

Answer

First, the last sentence is excellent: We should definitely be aiming for a general consistent answer where possible so we can spell the same thing the same way throughout our code.

In questions about binders, the usual answer is to use a lambda function directly instead – and usually a generic lambda is the simplest and most flexible. A lambda additionally lets you more directly express how to take its parameters when it’s invoked – by value, by reference, by const, and so forth, instead of resorting to things like std::ref as we do when we use binders.

For the second example, you can write f as a named lambda this way:

auto f = [](const auto& x){ return x+1; };

For the first example, you can write sort_down as a named lambda like this:

auto sort_down = [](auto a, auto b){ return sort(a, b, [](int x, int y){return x > y;}); };

Note the way to give a name to a lambda: assign it to an auto variable, which you can give any name you like. In this case I take a and b by value because we know they’re intended to be iterators which are supposed to be cheap to copy.

The nice thing about lambdas is they allow exactly what you asked for: consistency. To be consistent, code should use lambdas exclusively, never bind. As of C++14, which added generic lambdas, lambdas can now do everything binders can do and more, so there is never a reason to use the binders anymore.

Note that the old binders bind1st and bind2nd were deprecated in C++11 and removed in C++17. Granted, we have not yet deprecated or removed std::bind itself, but I wouldn’t be surprised to see that removed too. Although bind can be convenient and it’s not wrong to use it, there is no reason I know of to use it in new code that is not now covered by lambdas, and since lambdas can do things that binders cannot, we should encourage and use lambdas consistently.

As a side point, notice that the “greater than” comparison lambda

[](int x, int y){return x > y;}

expects integer values only, and because of the glories of the C integer types it can give the wrong results because of truncating (e.g., if passed a long long) and/or sign conversion (e.g., a 32-bit unsigned 3,000,000,000 is greater than 5, but when converted to signed is less than 5). It would be better written as

[](const auto& x, const auto& y){return x > y;}

or in this case

std::greater<>{}

Thanks to Stephan Lavavej for comments on this answer.

Your questions?

If you have any question about C++ in general, please comment about it below. Someone in the community may answer it, or someone on our team may consider it for a future blog post. If instead your question is about support for a Microsoft product, you can provide feedback via Help > Report A Problem in the product, or via Developer Community.

Category
C++

Author

0 comments

Discussion are closed.