std::map with values of different function-types

246 Views Asked by At

I want to use a std::map with certain types of functions (certain, specific parameter types) as possible values. However, the following (minimal) example does not compile.

Why is that and how can I allow FunctionType<bool> and FunctionType<int> as possible values for the Map?

#include <functional>
#include <iostream>
#include <map>
#include <string>
#include <variant>


template <typename T>
using FunctionType = std::function<T(T)>;


int main()
{
  auto myMap = std::map<std::string, std::variant<FunctionType<bool>, FunctionType<int>>>
    {
     {"foo", [](bool x){return x;}},
     {"bar", [](int x){return x;}},
    };

  return 0;
}

Error message:


$ g++ --std=c++2a -o /tmp/variant-test /tmp/variant-test.cpp && /tmp/variant-test

variant-test.cpp: In function ‘int main()’:
variant-test.cpp:18:5: error: no matching function for call to ‘std::map<std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > >::map(<brace-enclosed initializer list>)’
   18 |     };
      |     ^
In file included from /usr/include/c++/10/map:61,
                 from variant-test.cpp:3:
/usr/include/c++/10/bits/stl_map.h:290:2: note: candidate: ‘template<class _InputIterator> std::map<_Key, _Tp, _Compare, _Alloc>::map(_InputIterator, _InputIterator, const _Compare&, const allocator_type&) [with _InputIterator = _InputIterator; _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  290 |  map(_InputIterator __first, _InputIterator __last,
      |  ^~~
/usr/include/c++/10/bits/stl_map.h:290:2: note:   template argument deduction/substitution failed:
variant-test.cpp:18:5: note:   candidate expects 4 arguments, 2 provided
   18 |     };
      |     ^
In file included from /usr/include/c++/10/map:61,
                 from variant-test.cpp:3:
/usr/include/c++/10/bits/stl_map.h:273:2: note: candidate: ‘template<class _InputIterator> std::map<_Key, _Tp, _Compare, _Alloc>::map(_InputIterator, _InputIterator) [with _InputIterator = _InputIterator; _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  273 |  map(_InputIterator __first, _InputIterator __last)
      |  ^~~
/usr/include/c++/10/bits/stl_map.h:273:2: note:   template argument deduction/substitution failed:
variant-test.cpp:18:5: note:   couldn’t deduce template parameter ‘_InputIterator’
   18 |     };
      |     ^
In file included from /usr/include/c++/10/map:61,
                 from variant-test.cpp:3:
/usr/include/c++/10/bits/stl_map.h:256:2: note: candidate: ‘template<class _InputIterator> std::map<_Key, _Tp, _Compare, _Alloc>::map(_InputIterator, _InputIterator, const allocator_type&) [with _InputIterator = _InputIterator; _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  256 |  map(_InputIterator __first, _InputIterator __last,
      |  ^~~
/usr/include/c++/10/bits/stl_map.h:256:2: note:   template argument deduction/substitution failed:
variant-test.cpp:18:5: note:   candidate expects 3 arguments, 2 provided
   18 |     };
      |     ^
In file included from /usr/include/c++/10/map:61,
                 from variant-test.cpp:3:
/usr/include/c++/10/bits/stl_map.h:250:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map(std::initializer_list<std::pair<const _Key, _Tp> >, const allocator_type&) [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >; std::map<_Key, _Tp, _Compare, _Alloc>::allocator_type = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  250 |       map(initializer_list<value_type> __l, const allocator_type& __a)
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:250:40: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘std::initializer_list<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >’
  250 |       map(initializer_list<value_type> __l, const allocator_type& __a)
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
/usr/include/c++/10/bits/stl_map.h:244:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map(std::map<_Key, _Tp, _Compare, _Alloc>&&, const allocator_type&) [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >; std::map<_Key, _Tp, _Compare, _Alloc>::allocator_type = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  244 |       map(map&& __m, const allocator_type& __a)
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:244:17: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘std::map<std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > >&&’
  244 |       map(map&& __m, const allocator_type& __a)
      |           ~~~~~~^~~
/usr/include/c++/10/bits/stl_map.h:240:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map(const std::map<_Key, _Tp, _Compare, _Alloc>&, const allocator_type&) [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >; std::map<_Key, _Tp, _Compare, _Alloc>::allocator_type = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  240 |       map(const map& __m, const allocator_type& __a)
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:240:22: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘const std::map<std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > >&’
  240 |       map(const map& __m, const allocator_type& __a)
      |           ~~~~~~~~~~~^~~
/usr/include/c++/10/bits/stl_map.h:236:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map(const allocator_type&) [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >; std::map<_Key, _Tp, _Compare, _Alloc>::allocator_type = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  236 |       map(const allocator_type& __a)
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:236:7: note:   candidate expects 1 argument, 2 provided
/usr/include/c++/10/bits/stl_map.h:228:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map(std::initializer_list<std::pair<const _Key, _Tp> >, const _Compare&, const allocator_type&) [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >; std::map<_Key, _Tp, _Compare, _Alloc>::allocator_type = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  228 |       map(initializer_list<value_type> __l,
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:228:40: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘std::initializer_list<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >’
  228 |       map(initializer_list<value_type> __l,
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
/usr/include/c++/10/bits/stl_map.h:215:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map(std::map<_Key, _Tp, _Compare, _Alloc>&&) [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  215 |       map(map&&) = default;
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:215:7: note:   candidate expects 1 argument, 2 provided
/usr/include/c++/10/bits/stl_map.h:207:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map(const std::map<_Key, _Tp, _Compare, _Alloc>&) [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  207 |       map(const map&) = default;
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:207:7: note:   candidate expects 1 argument, 2 provided
/usr/include/c++/10/bits/stl_map.h:194:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map(const _Compare&, const allocator_type&) [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >; std::map<_Key, _Tp, _Compare, _Alloc>::allocator_type = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  194 |       map(const _Compare& __comp,
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:194:27: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘const std::less<std::__cxx11::basic_string<char> >&’
  194 |       map(const _Compare& __comp,
      |           ~~~~~~~~~~~~~~~~^~~~~~
/usr/include/c++/10/bits/stl_map.h:185:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map() [with _Key = std::__cxx11::basic_string<char>; _Tp = std::variant<std::function<bool(bool)>, std::function<int(int)> >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::variant<std::function<bool(bool)>, std::function<int(int)> > > >]’
  185 |       map() = default;
      |       ^~~
/usr/include/c++/10/bits/stl_map.h:185:7: note:   candidate expects 0 arguments, 2 provided
1

There are 1 best solutions below

2
On BEST ANSWER

This has nothing to do with the one-user-defined-conversion rule. It's easy to see this by taking your example and replacing FunctionType<int> with FunctionType<std::string> - everything compiles.

variant<X, Y> is implicitly convertible from everything that converts to X or Y, provided that it can unambiguously determine the type to convert to. The proviso is important because std::function doesn't require exact matches: a std::function<bool(bool)> accepts everything that can be called with a bool and returns something that can be converted to a bool. Obviously [](bool x) { return x; } meets this requirement, but [](int x) { return x; } does so too. Similarly, both of these lambdas can be converted to a std::function<int(int)> because they can both be called with an int to produce something that's convertible to an int.

Since the lambdas are convertible to both alternatives, and there is no ordering between the two, variant will just refuse to convert from it. This is why replacing FunctionType<int> with FunctionType<std::string> makes it compile - only the bool alternative is viable in that case.