2
\$\begingroup\$

I created 3 different implementations (which produce the same results) using the count_if function from STL. I wish to check the number of elements in the vector which are strictly less than 6.

These are:

  1. C++ lambda function
  2. Global function (no functor)
  3. Using class and creating my own functor

These implementations work, but, how can I improve them into a more efficient and robust solutions?

#include <vector> #include <algorithm> #include <numeric> #include <functional> #include <iostream> using namespace std; // GLOBAL function (not FUNCTOR) See below for FUNCTOR implementation! template <int num> bool lessThan(int x) { return(x < num); // return TRUE if the value is less than NUM } // Paramterized Function with more robust FUNCTOR implementation (written by me) (I know stl has this built in) class LessThan { private: int m_x; // the value we wish to be less than public: LessThan(int val) : m_x(val) {} // custom constructor: assign a value to x bool operator() (int upper) // all values must be strictly less than the upper bound { return (m_x < upper); } }; int main() { vector<int> vec = { 2, 7, 4, 5, 7, 7}; // Using new C++ Lambda function implementation int x = count_if(vec.begin(), vec.end(), [](int x) {return x < 6; }); // using C++ Lambda Function (just a function with no name) cout <<"There exists " << x << " integers in vec which are strictly less than 6." << endl; // Using global lessThan6 implementation (NOT FUNCTOR) int y = count_if(vec.begin(), vec.end(), lessThan<6>); // not very robust cout << "There exists " << y << " integers in vec which are strictly less than 6." << endl; // Using MY OWN FUNCTOR implementation int z = count_if(vec.begin(), vec.end(), LessThan(6)); // robust cout << "There exists " << z << " integers in vec which are strictly less than 6." << endl; } 
\$\endgroup\$
1
  • \$\begingroup\$You don't say why you have 3 different implementations. Do you need 3? If not, then the one that uses the lambda expression is the best. The others are redundant.\$\endgroup\$
    – ksl
    CommentedJul 16, 2015 at 10:32

1 Answer 1

3
\$\begingroup\$

I'm not sure entirely what you're asking. If you're asking for which of three is "better" code between:

int x = count_if(vec.begin(), vec.end(), [](int x) {return x < 6; }); int y = count_if(vec.begin(), vec.end(), lessThan<6>); int z = count_if(vec.begin(), vec.end(), LessThan(6)); 

I would say x then z then y. The reason for this is that the lambda solution is the only one where the actual functor is in-line with the algorithm. Anybody reading that code knows exactly what x should be immediately. There is no question. It is clear (to the extent that lambda syntax is) and concise.

z would be the C++03 way of doing this thing. It's... ok. We wrote that code for many years. The problem is the body of the functor is often hundreds of lines removed from the call, maybe in a different file... so readers of your code will have to find it. And it's never going to be perfectly clear either (who knows, maybe you named your functor weird and it's returning 6 < x? Naming things is really hard)

y is the worst. Having 6 as a template argument adds nothing to your performance (it's not like you're optimizing out a comparison), has equal readability loss compared to the lambda with the functor, and on top of that, can't even be called with a variable. With the other two, I can easily write:

int threshold = get_threshold(); int x = count_if(vec.begin(), vec.end(), [=](int x) {return x < threshold; }); int z = count_if(vec.begin(), vec.end(), LessThan(threshold)); 

But there's no way to pass threshold in as a template.

Typically, for algorithms, strongly prefer lambda unless (1) the algorithm call is a small part of the function and (2) the functor is very long and complicated. I say both must be met because something like this is still perfectly acceptable in my opinion:

int countBySomeCriteria() { return count_if(vec.begin(), vec.end(), [=](Foo const&) { /* some 15 line complicated functor */ }); } 

whereas if that call were surrounded by a lot of other logic, it would be much harder to easily make out.

Lambdas got added to the language basically to make stuff like this easier to write and easier to read. You should use them because they succeed in that goal.


PS. There is a fourth way to pass a functor: the lambda that was a lambda before C++11 lambdas: Boost.Lambda:

int q = count_if(vec.begin(), vec.end(), _1 < 6); 

Make of that what you will.

\$\endgroup\$
5
  • 2
    \$\begingroup\$Good review. One minor nit is that I'd be inclined to capture threshold explicitly by value [threshold](int x){return x < threshold; } rather than capturing all automatic variables as with [=]. See en.cppreference.com/w/cpp/language/lambda for more details on the options.\$\endgroup\$
    – Edward
    CommentedJul 16, 2015 at 11:23
  • 1
    \$\begingroup\$so readers of your code will have to find it. Anybody using a decent editor (and we are all engineers so we are using good editors) should be one click away from the source. If I want to find it <ctrl> ] takes me directly to the source.\$\endgroup\$CommentedJul 17, 2015 at 17:00
  • \$\begingroup\$Otherwise totally agree: Prefer Lambda as long as it is short. Short is a relative term and will depend on context.\$\endgroup\$CommentedJul 17, 2015 at 17:05
  • \$\begingroup\$@Barry: You are implying that it is hard to find and inconvenient (true if you are using notepad but otherwise not). <ctrl> ]:sp<return>:pop<return> Takes me less than a second to get the code sitting side by side with the call point.\$\endgroup\$CommentedJul 17, 2015 at 17:19
  • \$\begingroup\$@Barry: You spent a whole paragraph on the topic to explain as a reason to explain why lambdas are better. So if finding the code nor comparing it against the original is not a hindrance there is no need to mention it.\$\endgroup\$CommentedJul 17, 2015 at 17:48

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.