Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda and map, param by reference - compile error

Tags:

c++

c++11

lambda

I've tried to narrow down my problem to a minimal example:

#include <algorithm>
#include <map>
#include <string>
#include <vector>

int main()
{
    std::vector<int> result;
    std::map<std::string, std::pair<unsigned int, std::vector<int>>> other;

    if (true)
    {
        std::for_each(other.begin(), other.end(),
            [&](std::pair<std::string, std::pair<unsigned int, std::vector<int>>> & data)
            {
                result.insert(result.end(), data.second.second.begin(), data.second.second.end());
            });
    }

    return 0;
}

I get a compiler error:

error C2664: 'void main::<lambda_1b93236899a42921c1aec8d5288e5b90>::operator ()(std::pair<std::string,std::pair<unsigned int,std::vector<int,std::allocator<_Ty>>>> &) const': cannot convert argument 1 from 'std::pair<const _Kty,_Ty>' to 'std::pair<std::string,std::pair<unsigned int,std::vector<int,std::allocator<_Ty>>>> &'

As far as I can tell the lambda parameter is indeed a reference to the type that is contained by the map which we are iterating through. That is the type I am supposed to be using, right?

If I remove the amperstand before data it compiles.

Why?

I do not want to pass each element by value, as those collections will contain a lot of data in my real program.

If I replace the lambda param with auto & it compiles, which leads me to believe the type in the lambda param does not match the type contained by the map, but it sure looks like it does to me. Also, why would the original compile without & if the type is wrong?

What am I missing/not understanding?

like image 542
Christopher Pisz Avatar asked Jan 05 '23 00:01

Christopher Pisz


2 Answers

std::map<Key, T>'s value_type is std::pair<const Key, T>, not std::pair<Key, T>. The version without an ampersand makes a copy of each pair. Since you can copy const Key to Key everything is fine. Add a const to your lambda's parameter type.

#include <algorithm>
#include <map>
#include <string>
#include <vector>

int main()
{
    std::vector<int> result;
    std::map<std::string, std::pair<unsigned int, std::vector<int>>> other;

    if (true)
    {
        std::for_each(other.begin(), other.end(),
            [&](std::pair<const std::string, std::pair<unsigned int, std::vector<int>>> & data)
        // Add this const ^^^^^
        {
            result.insert(result.end(), data.second.second.begin(), data.second.second.end());
        });
    }

    return 0;
}
like image 63
François Andrieux Avatar answered Jan 13 '23 19:01

François Andrieux


I've been strugglig with the very same problem today.

I solved that making the key value type in the std::pair const:

[&](std::pair<const std::string, std::pair<unsigned int, std::vector<int>>> & data)
           // ^^^^^

After thinking about that a bit, it's quite logical:
You cannot change the key_value of a std::map's entry to something different. So if it's working with an auto loop and a reference it needs to be specified as const.

like image 24
πάντα ῥεῖ Avatar answered Jan 13 '23 19:01

πάντα ῥεῖ