1

Is it possible to use a generic templatized lambda as value (std::function) in an unordered map?

I want to achieve to scan a file for keywords and then read the corresponding data from the file in a dedicated variable/place in a data structure.

I created a minimum reproducible example, where I use a std::tuple as the data structure and a normal template function as the value (using std::function) in an std::unordered_map.

Please see the simple working code:

#include <iostream>
#include <sstream>
#include <unordered_map>
#include <string>
#include <functional>
#include <tuple>
using namespace std::string_literals;

// The tuple (instead of using a struct)
using Data = std::tuple<int, int, long, double>;

// Generic read for the above defined tuple
template <std::size_t Index>
std::istream& read(std::istream& is, Data& d) { is >> std::get<Index>(d); return is; }

// Test data
std::istringstream iss{ R"(int0   0
int1   1
some nonsense in the data
long   2
double   3.3)" };

// Test code
int main() {
    // Our data structure
    Data data{};

    // Map the read function to a pattern or keyword
    std::unordered_map<std::string, std::function<std::istream& (std::istream&, Data&)>> reader{{"int0"s,read<0>},{"int1"s,read<1>},{"long"s,read<2>},{"double"s,read<3>}};

    // Read all data
    for (std::string type{}; iss >> type; reader.contains(type) and reader[type](iss,data))
        ;

    // Debug output
    std::cout << std::get<0>(data) << '\n' << std::get<1>(data) << '\n' << std::get<2>(data) << '\n' << std::get<3>(data) << '\n';
}

You can run the above code in compiler explorer.


I would like to use a generic template lambda instead of the global "read" function, and put that, as value, via a std::function into the std::unordered_map.

I found a similar problem here. But there, the template parameter is used as argument in the lambda, which would influence the std::functions signature.

So, I am really not sure.

Is my idea achievable at all? And if, how?

Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51
A M
  • 14,694
  • 5
  • 19
  • 44
  • *I would like to* Is there something that prevents you from trying? If not, what errors are you getting? Please post code that **does not work**. Do **not** post code that works along with an explanation of how you would like to change it. – n. m. could be an AI Apr 01 '23 at 10:17
  • 2
    @n.m. My low know how of the C++ language prevents me to find a solution. I tried for several hours, and could not find a solution. My hope was that the community could maybe help me. – A M Apr 01 '23 at 10:23
  • 2
    Let's try again. What exactly have you tried? Show your failed attempts. – n. m. could be an AI Apr 01 '23 at 10:36
  • At the very least, show an example of that "generic template lambda" that you wish to use in place of `read`. – Igor Tandetnik Apr 01 '23 at 14:25
  • 1
    Are you thinking of something like `auto generic_read = [](std::istream& is, Data& d)...`? Such a lambda is difficult to call - one has to write `generic_read.operator()<0>(iss, data);`, for example. `std::function` doesn't know how to do that, it just applies straight function call operator to its callable. So you would have to introduce another function or lambda wrapping this generic lambda and calling it with this explicit syntax. It could be made to work in the end, but the result would be pretty horrifying. Why do you want this? What problem do you think it'll solve? – Igor Tandetnik Apr 01 '23 at 14:47
  • @IgorTandetnik Yes, excactly. I tried it here: https://godbolt.org/z/MKd3v8x54 . But, it may not work, as the suggested link in my question implies. I guess, because `std::function` cannot hold such a lambda. I wanted to achieve, to make the global function local (as a lambda). I am really wondering that it is so complicated. I did not expect that. – A M Apr 01 '23 at 15:09
  • 1
    `read<0>` doesn't work because `read` is not itself a template - it's an instance of a class with templated `operator()`. And because the template parameter of that `operator()` cannot be deduced from the arguments, `read` doesn't meet *Invokable* requirements; it cannot be called simply as `read(some args)`. Whereas `std::function` requires its target to be *Invokable*. This is why your attempt doesn't work. – Igor Tandetnik Apr 01 '23 at 17:41

1 Answers1

2

Something like this:

template <std::size_t Index, typename F>
auto wrap(F f) {
  return [=]<typename ... Args>(Args&&... args) -> decltype(auto) {
    return f.template operator()<Index>(std::forward<Args...>(args...));
  };
}

Now you can do

auto read = [&]<std::size_t Index>(std::istream& is) -> std::istream& {
    is >> std::get<Index>(data); return is;
};

std::unordered_map<std::string, std::function<std::istream& (std::istream&)>> reader{
    {"int0"s,wrap<0>(read)},
    {"int1"s,wrap<1>(read)},
    {"long"s,wrap<2>(read)},
    {"double"s,wrap<3>(read)}};

Demo

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • 1
    What a great answer!!! But I need to digest it. It seems that I need to learn more about the lambdas and their underlying implementation. And your solution is rather generic. It can be adapted to many more related problems. Really impressive. – A M Apr 02 '23 at 08:00