I'm trying to nest boost's “map_list_of” in C++03, but apparently construction is ambiguous?

筅森魡賤 提交于 2019-12-03 13:14:12
dyp

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 on std::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>()
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!