问题
Consider this:
#include <iostream>
#include <map>
#include <string>
#include <boost/assign/list_of.hpp>
using boost::assign::map_list_of;
const std::map<int, std::map<int, char> > test = map_list_of
(100, map_list_of
(1, 'a')
(2, 'b')
)
(101, map_list_of
(1, 'c')
(2, 'd')
)
;
int main()
{
std::cout << test.find(101)->second.find(2)->second << "\n";
}
I wanted the result to be a program that, when executed, outputs d
.
Instead, I get this:
$ clang++ -std=c++03 -O2 -Wall -pedantic -pthread main.cpp
In file included from main.cpp:1:
In file included from /usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/iostream:39:
In file included from /usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/ostream:38:
In file included from /usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/ios:40:
In file included from /usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/char_traits.h:39:
In file included from /usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/stl_algobase.h:64:
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/stl_pair.h:119:22: error: call to constructor of 'std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > >' is ambiguous
: first(__p.first), second(__p.second) { }
^ ~~~~~~~~~~
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/stl_tree.h:1843:29: note: in instantiation of function template specialization 'std::pair<const int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > > >::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > >' requested here
_M_insert_unique_(end(), *__first);
^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/stl_map.h:255:16: note: in instantiation of function template specialization 'std::_Rb_tree<int, std::pair<const int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > > >, std::_Select1st<std::pair<const int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > > > >, std::less<int>, std::allocator<std::pair<const int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > > > > >::_M_insert_unique<std::_Deque_iterator<std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > >, std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > &, std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > *> >' requested here
{ _M_t._M_insert_unique(__first, __last); }
^
/usr/local/include/boost/assign/list_of.hpp:163:20: note: in instantiation of function template specialization 'std::map<int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > >, std::less<int>, std::allocator<std::pair<const int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > > > > >::map<std::_Deque_iterator<std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > >, std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > &, std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > *> >' requested here
return Container( begin(), end() );
^
/usr/local/include/boost/assign/list_of.hpp:142:20: note: in instantiation of function template specialization 'boost::assign_detail::converter<boost::assign_detail::generic_list<std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > >, std::_Deque_iterator<std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > >, std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > &, std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > *> >::convert<std::map<int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > >, std::less<int>, std::allocator<std::pair<const int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > > > > > >' requested here
return convert<Container>( c, tag_type() );
^
/usr/local/include/boost/assign/list_of.hpp:436:49: note: in instantiation of function template specialization 'boost::assign_detail::converter<boost::assign_detail::generic_list<std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > >, std::_Deque_iterator<std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > >, std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > &, std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > *> >::convert_to_container<std::map<int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > >, std::less<int>, std::allocator<std::pair<const int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > > > > > >' requested here
return this-> BOOST_NESTED_TEMPLATE convert_to_container<Container>();
^
main.cpp:7:50: note: in instantiation of function template specialization 'boost::assign_detail::generic_list<std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> > > >::operator map<std::map<int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > >, std::less<int>, std::allocator<std::pair<const int, std::map<int, char, std::less<int>, std::allocator<std::pair<const int, char> > > > > > >' requested here
const std::map<int, std::map<int, char> > test = map_list_of
^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/stl_map.h:171:7: note: candidate constructor
map(const _Compare& __comp,
^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/stl_map.h:182:7: note: candidate constructor
map(const map& __x)
^
1 error generated.
(similar results under GCC)
How can I resolve this?
I get a similar error even if I use std::map<int, char>(map_list_of(...))
instead of map_list_of(...)
for those inner maps.
回答1:
C++03 defines two constructors for map
that can be called with one argument [lib.map]p2:
explicit map(const Compare& comp = Compare(), const Allocator& = Allocator()); // [...] map(const map<Key,T,Compare,Allocator>& x);
boost's map_list_of
creates an object of a generic_list
class template instantiation, from the recent SVN:
template< class Key, class T > inline assign_detail::generic_list< std::pair < BOOST_DEDUCED_TYPENAME assign_detail::assign_decay<Key>::type, BOOST_DEDUCED_TYPENAME assign_detail::assign_decay<T>::type > > map_list_of( const Key& k, const T& t )
Where the primary generic_list
template contains the following conversion operator:
template< class Container > operator Container() const { return this-> BOOST_NESTED_TEMPLATE convert_to_container<Container>(); }
Both map
constructors are viable, as this operator allows conversion to both map
and Compare
. As far as I know, you cannot SFINAE-constrain a conversion operator in C++03.
The map
is constructed explicitly when inserting a new node in the outer map. A pair of iterators is used to iterate over the inner generic_list
to construct the outer map
. Dereferencing this iterator yields a std::pair<int, boost::assign_detail::generic_list<std::pair<int, char> >
. The node (value) type of the outer map is std::pair<int const, std::map<int, char> >
.
Therefore, the compiler tries to construct the latter type from the former. In C++03, this pair
constructor is not SFINAE-constrained, since that's not possible in C++03. [lib.pairs]p1
template<class U, class V> pair(const pair<U, V> &p);
libstdc++ implements this as follows:
template<class _U1, class _U2>
pair(const pair<_U1, _U2>& __p)
: first(__p.first), second(__p.second) { }
I'm not entirely sure if that's compliant, since [lib.pairs]p4
Effects: Initializes members from the corresponding members of the argument, performing implicit conversions as needed.
(But, as I said, SFINAE on ctors cannot be implemented in C++03.)
In C++11 and 14, this also fails, but for a different reason. Here, the pair constructors are SFINAE-constrained. But the constrain requires implicit convertibility (is_convertible
), while the program has UB if the target pair of types cannot be constructed from the sources (is_constructible
). I've written a bit more about this issue in another SO answer. Interestingly, a proposed solution N4387 to the issue mentioned in that other question says:
It should be noted here, that for the general case the
std::is_constructible<T, U>::value
requirement for the non-explicit constructor which is constrained onstd::is_convertible<U, T>::value
is not redundant, because it is possible to create types that can be copy-initialized but not direct-initialized
This is exactly the case we run into here: A map
can be copy-initialized from a generic_list
, since this makes the explicit
constructor non-viable. But a map
cannot be direct-initialized from generic_list
, since this makes the conversion ambiguous.
As far as I can see, N4387 does not solve the problem in the OP. On the other hand, with uniform initialization, we have an alternative to map_list_of
. And we can SFINAE-constrain conversion operators since C++11.
One solution is to eliminate the explicit
constructor by only allowing implicit conversions:
template<typename T> T implicit_cast(T t) { return t; }
implicit_cast<InnerMap>( map_list_of(1, 'a')(2, 'b') )
But there's a more direct way: simply use the convert_to_container
member function of generic_list
's base class converter
(also a class template):
map_list_of(1, 'a')(2, 'b').convert_to_container<InnerMap>()
来源:https://stackoverflow.com/questions/29988277/im-trying-to-nest-boosts-map-list-of-in-c03-but-apparently-construction-i