为什么 ::boost::tie 不能与 BOOST_FOREACH 一起使用?

3

我想使用 BOOST_FOREACH 来遍历一个 boost::ptr_map,并发现了 这个看起来很不错的解决方案。我更喜欢使用它来提高可读性,而不是其他给出的解决方案。我编写了以下代码:

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;
}

然而,这段代码无法编译,并给我以下错误(完整的错误信息还有几行,请告诉我是否需要粘贴它们):
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)

看起来建议的解决方案对少数人有效,但我无法弄清楚为什么对我无效。我在这里做错了什么?

(注意:我正在处理一个史前项目,所以只能使用C++03。g++版本:4.8.4)


不检查内部结构就不能确定,但很可能与无法使用std::tuple做到这一点的原因相同。它只能使用编译时构造进行迭代。 - NathanOliver
我对这个看起来很整洁的解决方案是否有效表示怀疑,因为它期望迭代器给我们一个 std::pair,但我们只得到了一些类似于它的东西(https://github.com/boostorg/ptr_container/blob/develop/include/boost/ptr_container/detail/map_iterator.hpp#L34)。 (而且自那个答案发布以来,代码似乎没有改变) - Dan Mašek
然而,您可以实现自己的简单的“tie”等效物--类似于这样的东西(http://coliru.stacked-crooked.com/a/e01dca4a777269d0)。 - Dan Mašek
@Dan 当然有这种可能性,但我希望在没有检查是否正确的情况下不会有7个人点赞。嗯... - Masked Man
@MaskedMan 经过对 Boost 报告的进一步挖掘,我非常有信心它从未能够工作。至于你所说的话,很遗憾,但是我看到了大量垃圾回答得到了赞同,也看到了各种程度的懒惰,因此我倾向于将投票结果视为参考 -- 特别是当答案没有任何参考资料、工作示例(最好是实时的)等时。 - Dan Mašek
1个回答

2

问题应该是“为什么boost::tie不能与boost::ptr_map(或其迭代器的解引用结果)一起使用?”--在所有这些中,BOOST_FOREACH是相当无辜的。

调查

如果我们看一下Boost的版本历史记录,我们可以看到Tuple出现在版本1.24.0中,而Pointer Container出现在版本1.33.0中。

Tuple

Github上相关的元组相关代码:

通过研究代码,我们可以得出以下观察结果:

  • tie 一直创建一个 tuple[1][2]

  • tuple 一直派生自模板 cons[1][2]

  • tuple(和cons)始终具有赋值运算符,可以接受cons(即另一个tuple[1][2]std::pair[1][2]--没有其他的。

指针容器

Github上与指针容器相关的代码:

通过研究这些代码,我们可以得出以下观察结果:

  • 在前两个版本中(1.33.x),解引用迭代器会给我们一个指向值的引用[1] [2]
  • 自第三个版本(1.34.0)以来,我们得到了一个ref_pair,看起来有点像std::pair,但实际上并不是[1][2][3]

结论

我们可以通过只进行一次迭代来消除BOOST_FOREACH,但仍然会得到相同的错误:

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

根据我们之前学到的知识,我们知道这等同于

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

我们也知道*int2strMap.begin()将会得到一个std::string引用或者一个ref_pair
由于元组没有赋值运算符可以接受这两种类型,所以建议的代码片段无法在任何现有版本的Boost中编译。

解决方法

受到boost::tupleboost::tie实现的启发,我们可以编写一个简单的reference_pair模板,它保存两个引用并允许分配任何看起来像pair(即具有firstsecond成员)的东西,以及一个帮助函数tie,它将创建reference_pair实例。

示例代码

#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;
    }
}

在 Coliru 上实时运行

控制台输出

0 one
1 two
2 three

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接