Why doesn't ::boost::tie work with BOOST_FOREACH?

扶醉桌前 提交于 2019-12-23 15:55:25

问题


I want to use BOOST_FOREACH to iterate over a boost::ptr_map, and came across this neat-looking solution. I would prefer using this for better readability, as against the other solutions given. I wrote the following code:

boost::ptr_map<int, std::string> int2strMap;
int x = 1;
int2strMap.insert(x, new std::string("one"));
int one;
std::string* two;
BOOST_FOREACH(::boost::tie(one, two), int2strMap)
{
   std::cout << one << two << std::endl;
}

However, this fails to compile, and gives me the below error (The full error message has several more lines, let me know if I should paste them.):

error: no match for 'operator=' (operand types are 'boost::tuples::detail::tie_mapper<int, std::basic_string<char>*, void, void, void, void, void, void, void, void>::type {aka boost::tuples::tuple<int&, std::basic_string<char>*&, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type>}' and 'boost::iterators::iterator_reference<boost::ptr_map_iterator<std::_Rb_tree_iterator<std::pair<const int, void*> >, int, std::basic_string<char>* const> >::type {aka boost::ptr_container_detail::ref_pair<int, std::basic_string<char>* const>}')
BOOST_FOREACH(::boost::tie(one, two), int2strMap)

It appears that the suggested solution works for a handful of people, and I am unable to figure out why it doesn't work for me. What am I doing wrong here?

(Note: I am working on a prehistoric project, so stuck with using C++03. g++ version: 4.8.4)


回答1:


The question really should be "Why doesn't boost::tie work with boost::ptr_map (or rather the result of dereferencing its iterator)?" -- BOOST_FOREACH is quite innocent in all this.

Investigation

If we look at the version history of Boost, we can see that Tuple appears in version 1.24.0 and Pointer Container in version 1.33.0.

Tuple

Relevant tuple related code in github:

  • recent release: https://github.com/boostorg/tuple/blob/boost-1.64.0/include/boost/tuple/detail/tuple_basic.hpp
  • first release: https://github.com/boostorg/tuple/blob/boost-1.24.0/include/boost/tuple/detail/tuple_basic.hpp

Studying the code, we can make the following observations:

  • tie has always created a tuple [1] [2]

  • tuple has always derived from template cons [1] [2]

  • tuple (and cons) always had assignment operators taking either a cons (i.e. another tuple) [1] [2] or a std::pair [1] [2] -- nothing else.

Pointer Container

Relevant pointer container related code in github:

  • recent release: https://github.com/boostorg/ptr_container/blob/boost-1.64.0/include/boost/ptr_container/detail/map_iterator.hpp
  • third release: https://github.com/boostorg/ptr_container/blob/boost-1.34.0/include/boost/ptr_container/detail/map_iterator.hpp
  • first release: https://github.com/boostorg/ptr_container/blob/boost-1.33.0/include/boost/ptr_container/detail/map_iterator.hpp

Studying the code, we can make the following observations:

  • In first two releases (1.33.x), dereferencing the iterator gave us a reference to the value [1] [2]
  • Since the third release (1.34.0), we get a ref_pair, which somewhat looks like a std::pair, but really isn't [1] [2] [3]

Conclusion

We can eliminate BOOST_FOREACH by just doing one iteration, and still get the same error:

boost::tie(one, two) = *int2strMap.begin();

Based on what we learned earlier, we know this is equivalent to

boost::tuple<int&, std::string*&>(one, two) = *int2strMap.begin();

We also know that *int2strMap.begin() will result in either a std::string reference, or a ref_pair.

Since tuple has no assignment operator that would take either of those, the proposed snippet can not compile with any existing version of Boost.


Workaround

Taking inspiration from the implementation of boost::tuple and boost::tie, we can write a simple reference_pair template that holds two references and allows assignment of anything that looks like a pair (i.e. has members first and second), along with a helper tie function that will create an instance of reference_pair.

Sample Code

#include <boost/ptr_container/ptr_map.hpp>
#include <boost/foreach.hpp>
#include <iostream>

namespace {

template<class T0, class T1>
struct reference_pair
{
    T0& first;
    T1& second;

    reference_pair(T0& t0, T1& t1) : first(t0), second(t1) {}

    template<class U>
    reference_pair& operator=(const U& src) {
        first = src.first;
        second = src.second;
        return *this;
    }
};

template<class T0, class T1>
inline reference_pair<T0, T1> tie(T0& t0, T1& t1)
{
    return reference_pair<T0, T1>(t0, t1);
}

}

int main()
{
    boost::ptr_map<int, std::string> int2strMap;
    int n(0);
    int2strMap.insert(n, new std::string("one"));
    int2strMap.insert(++n, new std::string("two"));
    int2strMap.insert(++n, new std::string("three"));

    int one;
    std::string* two;

    BOOST_FOREACH(tie(one, two), int2strMap)
    {
       std::cout << one << " " << *two << std::endl;
    }
}

Live on Coliru

Console Output

0 one
1 two
2 three


来源:https://stackoverflow.com/questions/44588615/why-doesnt-boosttie-work-with-boost-foreach

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!