c++11 sorting list using lambda - c++

While practicing the use of lambdas, I wrote this program which is supposed to sort a list of pairs by their second element (an int).
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;
int main()
{
list<pair <string, int>> s = {{"two", 2}, {"one", 1}, {"three", 3}};
sort(s.begin(), s.end(), [](pair<string,int> a, pair<string, int> b) -> bool {
return (a.second) > (b.second);
});
for_each(s.begin(), s.end(), [](pair<string, int> a) {
cout << a.first << " " << a.second << endl;
});
}
I get those errors, though:
c:\qt\qt5.2.0\tools\mingw48_32\lib\gcc\i686-w64-mingw32\4.8.0\include\c++\bits\stl_algo.h:5513: error: no match for 'operator-' (operand types are 'std::_List_iterator<std::pair<std::basic_string<char>, int> >' and 'std::_List_iterator<std::pair<std::basic_string<char>, int> >')
std::__lg(__last - __first) * 2, __comp);
^
c:\qt\qt5.2.0\tools\mingw48_32\lib\gcc\i686-w64-mingw32\4.8.0\include\c++\bits\stl_algo.h:2245: ошибка: 'void std::__final_insertion_sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = std::_List_iterator<std::pair<std::basic_string<char>, int> >; _Compare = main()::__lambda0]', declared using local type 'main()::__lambda0', is used but never defined [-fpermissive]
__final_insertion_sort(_RandomAccessIterator __first,
^
What is wrong with my code?

You may not use std::sort with sequential containers such as std::list or std::forward_list because they have no random access iterator that is required by the standard algorithm std::sort. By this reason the both containers have their own member functions sort.
In you case the code will look the following way:
#include <iostream>
#include <list>
#include <string>
using namespace std;
int main()
{
list<pair <string, int>> s = {{"two", 2}, {"one", 1}, {"three", 3}};
s.sort( []( const pair<string,int> &a, const pair<string,int> &b ) { return a.second > b.second; } );
for ( const auto &p : s )
{
cout << p.first << " " << p.second << endl;
}
}
Take into account that you need to include header <string> otherwise your program will not be compiled with other compilers.

std::sort requires random access iterators, which std::list does not have. But you can use std::list::sort instead.
s.sort([](const pair<string,int>& a, const pair<string,int>& b)
{
return (a.second) > (b.second);
});
where I have made the parameters of the predicate const references, since there is no need to copy them, and doing so might incur some unnecessary overhead.

Related

C++ Reuse Lambda as Compare function in `std::priority_queue`

When working with std::priority_queue, I tried to clear the contents of the priority queue like this:
#include <iostream>
#include <queue>
#include <vector>
using std::cout;
using std::priority_queue;
using std::vector;
int main() {
const auto comp = [](int a, int b) {
return a > b;
};
auto a = priority_queue<int, vector<int>, decltype(comp)>(comp);
a.push(10);
a.push(9);
a.push(8);
// 3
cout << a.size() << '\n';
a = priority_queue<int, vector<int>, decltype(comp)>(comp);
// 0
cout << a.size() << '\n';
return 0;
}
When compiling with Clang, I got an error:
In file included from tmp.cpp:1:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/queue:455:39: error: no viable overloaded '='
{c = _VSTD::move(__q.c); comp = _VSTD::move(__q.comp); return *this;}
~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~
tmp.cpp:19:7: note: in instantiation of member function 'std::priority_queue<int, std::vector<int>, const (lambda at tmp.cpp:9:23)>::operator=' requested here
a = priority_queue<int, vector<int>, decltype(comp)>(comp);
^
tmp.cpp:9:23: note: candidate function (the implicit copy assignment operator) not viable: 'this' argument has type 'std::priority_queue<int, std::vector<int>, const (lambda at tmp.cpp:9:23)>::value_compare' (aka 'const (lambda at tmp.cpp:9:23)'), but method is not marked const
const auto comp = [](int a, int b) {
^
1 error generated.
Why would this give me an error? And is there any way to fix it? Thanks.
decltype(comp) is a const type, const auto comp, that makes the priority_queue member variable storing comp to be constant, thus can't be re-assigned.
You might want
priority_queue<int, vector<int>, remove_cv_t<decltype(comp)>>(comp);
Or
auto comp = [](int a, int b) {
return a > b;
};
The copy assignment operator for lambdas are implicitly deleted. The fix:
std::function<bool(int, int)> comp = [](int a, int b) {
return a > b;
};

How to clear a `priority_queue` that uses user-defined compare?

How to clear a priority_queue that uses user-defined compare?
From std::priority_queue documentation, I reduced the use of priority_queue to the case I need (= queue with user-defined compare)
>> cat test.cpp
#include <functional>
#include <queue>
#include <vector>
#include <iostream>
#include <utility>
auto queue_cmp = [](std::pair<int, double> const& lhs,
std::pair<int, double> const& rhs) {
return lhs.second > rhs.second; // Custom order.
};
typedef std::priority_queue<std::pair<int, double>,
std::vector<std::pair<int, double>>,
decltype(queue_cmp)> custom_queue;
template<typename T>
void print_queue(T q) { // NB: pass by value so the print uses a copy
int s = 0;
while(!q.empty()) {
std::pair<int, double> elem = q.top();
std::cout << s << ": " << elem.first << ", " << elem.second << std::endl;
q.pop();
s++;
}
std::cout << '\n';
}
int main() {
custom_queue q(queue_cmp);
for (int n = 10; n < 20; n++) {
double val = static_cast <double>(rand())/(static_cast<double>(RAND_MAX));
q.push(std::pair<int, double>(n, val));
}
print_queue(q);
//q = custom_queue(queue_cmp);
}
This runs OK
>> g++ -o test test.cpp
>> ./test
0: 15, 0.197551
1: 18, 0.277775
2: 16, 0.335223
3: 11, 0.394383
4: 19, 0.55397
5: 17, 0.76823
6: 12, 0.783099
7: 13, 0.79844
8: 10, 0.840188
9: 14, 0.911647
Now I need to reset q so, following priority queue clear method, I uncomment the last line in test.cpp... And get this compilation error as if I get this correctly, copy constructors are deleted on lamdba:
>> g++ -o test test.cpp
test.cpp: In function ‘int main()’:
test.cpp:34:31: error: use of deleted function ‘std::priority_queue<std::pair<int, double>, std::vector<std::pair<int, double> >, <lambda(const std::pair<int, double>&, const std::pair<int, double>&)> >& std::priority_queue<std::pair<int, double>, std::vector<std::pair<int, double> >, <lambda(const std::pair<int, double>&, const std::pair<int, double>&)> >::operator=(std::priority_queue<std::pair<int, double>, std::vector<std::pair<int, double> >, <lambda(const std::pair<int, double>&, const std::pair<int, double>&)> >&&)’
34 | q = custom_queue(queue_cmp);
| ^
In file included from /usr/include/c++/12/queue:64,
from test.cpp:2:
/usr/include/c++/12/bits/stl_queue.h:498:11: note: ‘std::priority_queue<std::pair<int, double>, std::vector<std::pair<int, double> >, <lambda(const std::pair<int, double>&, const std::pair<int, double>&)> >& std::priority_queue<std::pair<int, double>, std::vector<std::pair<int, double> >, <lambda(const std::pair<int, double>&, const std::pair<int, double>&)> >::operator=(std::priority_queue<std::pair<int, double>, std::vector<std::pair<int, double> >, <lambda(const std::pair<int, double>&, const std::pair<int, double>&)> >&&)’ is implicitly deleted because the default definition would be ill-formed:
498 | class priority_queue
| ^~~~~~~~~~~~~~
/usr/include/c++/12/bits/stl_queue.h:498:11: error: use of deleted function ‘<lambda(const std::pair<int, double>&, const std::pair<int, double>&)>&<lambda(const std::pair<int, double>&, const std::pair<int, double>&)>::operator=(const<lambda(const std::pair<int, double>&, const std::pair<int, double>&)>&)’
test.cpp:7:19: note: a lambda closure type has a deleted copy assignment operator
7 | auto queue_cmp = [](std::pair<int, double> const& lhs,
| ^
Is there a way to reset q in this case?
EDIT
bool queue_cmp(std::pair<int, double> const& lhs,
std::pair<int, double> const& rhs) {
return lhs.second > rhs.second; // Custom order.
};
Doesn't help
Not in C++17.
C++20 gives capture-less lambdas a default constructor. But in earlier language versions, the constructor is deleted.
Really, you should not be using a lambda. Just use a named struct.
As noted above in C++17 capture-less lambdas do not have default constructor.
Since a lambda is actually an anonymous class with a operator() method, you can use a named struct for the same purpose.
Your comparator will become:
struct queue_cmp
{
bool operator()(std::pair<int, double> const& lhs, std::pair<int, double> const& rhs) const
{
return lhs.second > rhs.second; // Custom order.
};
};
The rest of the code is almost the same.
You need to drop the decltype because queue_cmp is already a type, and there's no need to supply an instance of the comparator when constructing a queue:
typedef std::priority_queue<std::pair<int, double>,
std::vector<std::pair<int, double>>,
queue_cmp> custom_queue;
template<typename T>
void print_queue(T q) { // NB: pass by value so the print uses a copy
int s = 0;
while (!q.empty()) {
std::pair<int, double> elem = q.top();
std::cout << s << ": " << elem.first << ", " << elem.second << std::endl;
q.pop();
s++;
}
std::cout << '\n';
}
int main() {
custom_queue q;
for (int n = 10; n < 20; n++) {
double val = static_cast <double>(rand()) / (static_cast<double>(RAND_MAX));
q.push(std::pair<int, double>(n, val));
}
print_queue(q);
q = custom_queue{};
}
Demo: https://godbolt.org/z/7eq86r915.

Why does std::foreach not work with a std::vector<bool>? [duplicate]

This question already has answers here:
Why isn't vector<bool> a STL container?
(6 answers)
Closed 4 years ago.
I have the following code snippet, which takes the std::vector<int> list and writes a zero in all vector elements. This example is working perfectly fine.
#include <vector>
#include <iostream>
#include <algorithm>
int main () {
std::vector<int> list {1, 1, 2};
auto reset = [](int & element){element = 0;};
auto print = [](int element) {std::cout << element << " ";};
std::for_each(list.begin(), list.end(), reset);
std::for_each(list.begin(), list.end(), print);
}
If I take change the type of the vector from int to bool, the code will not compile.
#include <vector>
#include <iostream>
#include <algorithm>
int main () {
std::vector<bool> list {true, true, false};
auto reset = [](bool & element){element = false;};
auto print = [](int element) {std::cout << element << " ";};
std::for_each(list.begin(), list.end(), reset);
std::for_each(list.begin(), list.end(), print);
}
https://godbolt.org/g/2EntgX
I don't understand the compiler error message:
/opt/compiler-explorer/gcc-7.2.0/lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/stl_algo.h:3884:2: error: no matching function for call to object of type '(lambda at
:7:18)'
__f(*__first);
^~~
:10:10: note: in instantiation of function template
specialization 'std::for_each:7:18)>' requested here
std::for_each(list.begin(), list.end(),reset);
^
:7:18: note: candidate function not viable: no known
conversion from 'std::_Bit_iterator::reference' (aka
'std::_Bit_reference') to 'bool &' for 1st argument
auto reset = [](bool & element){element = false;};
^
:7:18: note: conversion candidate of type 'void (*)(bool &)'
Why does std::foreach work with a std::vector<int>, but does not work with a std::vector<bool>?
Is the memory optimisation of an std::vector<bool> (see here ) part of the answer?
Reason
The problem stems from the fact that dereferencing an iterator that came from std::vector<bool> doesn't return bool&, but rather a proxy object. Thus, it is not regarded as stl container (thanks to #KillzoneKid).
Fix
Use auto element in the parameter list. In general, if you don't care about the type, use auto&& in the lambda parameter list.
#include <vector>
#include <iostream>
#include <algorithm>
int main () {
std::vector<bool> list {true, true, false};
auto reset = [](auto && element){element = false;};
auto print = [](int element) {std::cout<< element << " ";};
std::for_each(list.begin(), list.end(),reset);
std::for_each(list.begin(), list.end(),print);
}
Demo.
Trying to use auto& will trigger compilation error again, as the proxy returned is not lvalue, but rvalue. Thus, auto&& has even more benefits than usual.

Simple program using transform and lambdas isn't working

I'm getting these errors. Why?
In file included from /usr/include/c++/4.7/algorithm:63:0,
from prog.cpp:2:
/usr/include/c++/4.7/bits/stl_algo.h: In instantiation of ‘_OIter std::transform(_IIter, _IIter, _OIter, _UnaryOperation) [with _IIter = __gnu_cxx::__normal_iterator >; _OIter = std::back_insert_iterator >; _UnaryOperation = main()::]’:
prog.cpp:10:98: required from here
/usr/include/c++/4.7/bits/stl_algo.h:4951:2: error: no match for call to ‘(main()::) (int&)’
prog.cpp:10:64: note: candidates are:
In file included from /usr/include/c++/4.7/algorithm:63:0,
from prog.cpp:2:
/usr/include/c++/4.7/bits/stl_algo.h:4951:2: note: int (*)(int, int)
/usr/include/c++/4.7/bits/stl_algo.h:4951:2: note: candidate expects 3 arguments, 2 provided
prog.cpp:10:79: note: main()::
prog.cpp:10:79: note: candidate expects 2 arguments, 1 provided
#include <algorithm>
#include <vector>
int main()
{
std::vector<int> v {1, 2, 3, 4, 5, 6};
std::vector<int> r;
std::transform(v.begin(), v.end(), std::back_inserter(r), [] (int a, int b) { return a + b; });
}
This code is supposed to add up each pair of numbers and put it into the r vector but it's not working. Why is that?
You've picked the overload of transform() that works on a single range - and thus expects a unary functor to be provided as the last argument.
If what you want is to work on two ranges (perhaps two "copies" of the same range?), then you should do:
std::transform(v.begin(), v.end(), v.begin(),
// ^^^^^^^^^
std::back_inserter(r), [] (int a, int b) { return a + b; });
So here is the full code:
#include <algorithm>
#include <vector>
int main()
{
std::vector<int> v {1, 2, 3, 4, 5, 6};
std::vector<int> r;
std::transform(v.begin(), v.end(), v.begin(),
std::back_inserter(r), [] (int a, int b) { return a + b; });
}
And here is a compiling live example.
However, unless you just wanted to practice with the overload of std::transform() that operates on two input ranges, you could follow Konrad Rudolph's advice from the comments, and write:
std::transform(v.begin(), v.end(),
std::back_inserter(r), [] (int a) { return a * 2; });
If, instead, you want to perform a sum of each pair of consecutive elements and store the result in the r vector while using a transform-like approach, then you may resort to Boost.Range, and write something like:
#include <boost/range.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
// ...
namespace rng = boost::adaptors;
// This will fill r with values { 3, 7, 11 }
boost::transform(
v | rng::strided(2),
std::make_pair(v.begin() + 1, v.end()) | rng::strided(2),
std::back_inserter(r),
[] (int a, int b) { return a + b; });
Here is a live example.
Also, as pointed out by NeelBasu in the comments, instead of using std::make_pair() to define the second range, you could write a more expressive (and compact) version based on the sliced range adaptor:
boost::transform(
v | rng::strided(2),
v | rng::sliced(1, v.size()) | rng::strided(2),
std::back_inserter(r),
[] (int a, int b) { return a + b; });

map, lambda, remove_if

So, i've problem with std::map, lambda and stl algorithm(remove_if). Actually, same code with std::list or std::vector works well.
My test example :
#include <map>
#include <iostream>
#include <algorithm>
struct Foo
{
Foo() : _id(0) {}
Foo(int id) : _id(id)
{
}
int _id;
};
typedef std::map<int, Foo> FooMap;
int main()
{
FooMap m;
for (int i = 0; i < 10; ++i)
m[i + 100] = Foo(i);
int removeId = 6;
// <<< Error here >>>
std::remove_if(m.begin(), m.end(), [=](const FooMap::value_type & item) { return item.second._id == removeId ;} );
for (auto & item : m )
std::cout << item.first << " = " << item.second._id << "\n";
return 0;
}
Error message :
In file included from /usr/include/c++/4.6/utility:71:0,
from /usr/include/c++/4.6/algorithm:61,
from main.cxx:1:
/usr/include/c++/4.6/bits/stl_pair.h: In member function ‘std::pair<_T1, _T2>& std::pair<_T1, _T2>::operator=(std::pair<_T1, _T2>&&) [with _T1 = const int, _T2 = Foo, std::pair<_T1, _T2> = std::pair<const int, Foo>]’:
/usr/include/c++/4.6/bits/stl_algo.h:1149:13: instantiated from ‘_FIter std::remove_if(_FIter, _FIter, _Predicate) [with _FIter = std::_Rb_tree_iterator<std::pair<const int, Foo> >, _Predicate = main()::<lambda(const value_type&)>]’
main.cxx:33:114: instantiated from here
/usr/include/c++/4.6/bits/stl_pair.h:156:2: error: assignment of read-only member ‘std::pair<const int, Foo>::first’
I don't understand what's wrong here. So, i gladly to read some advices/directions about it. My goal - use new lambda-style with std::map and algorithms, such as remove_if.
g++ 4.6, -std=c++0x.
The problem is that std::map<K,V>::value_type is std::pair<const K, V>, aka .first is const and not assignable. Lambdas have nothing to do with the problem here.
std::remove_if "removes" items by moving the elements of the container around, so that everything that does not fit the predicate is at the front, before the returned iterator. Everything after that iterator is unspecified. It does that with simple assignment, and since you can't assign to a const variable, you get that error.†
The name remove can be a bit misleading and in this case, you really want erase_if, but alas, that doesn't exist. You'll have to make do with iterating over all items and erasing them by hand with map.erase(iterator):
for(auto it = map.begin(), ite = map.end(); it != ite;)
{
if(it->second._id == remove_id)
it = map.erase(it);
else
++it;
}
This is safe because you can erase individual nodes in the tree without the other iterators getting invalidated. Note that I did not increment the iterator in the for loop header itself, since that would skip an element in the case where you erase a node.
† By now, you should have noticed that this would wreak havoc in the std::map's ordering, which is the reason why the key is const - so you can't influence the ordering in any way after an item has been inserted.
You could use find and erase for the map. It's not as convenient as remove_if, but it might be the best you've got.
int removeId = 6;
auto foundIter = m.find(removeId);
// if removeId is not found you will get an error when you try to erase m.end()
if(foundIter != m.end())
{
m.erase(foundIter);
}