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
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>
withFunctionType<std::string>
- everything compiles.variant<X, Y>
is implicitly convertible from everything that converts toX
orY
, provided that it can unambiguously determine the type to convert to. The proviso is important becausestd::function
doesn't require exact matches: astd::function<bool(bool)>
accepts everything that can be called with abool
and returns something that can be converted to abool
. 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 astd::function<int(int)>
because they can both be called with anint
to produce something that's convertible to anint
.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 replacingFunctionType<int>
withFunctionType<std::string>
makes it compile - only thebool
alternative is viable in that case.