【问题标题】:auto it = vector.begin() resulting type is not convertible to const_iteratorauto it = vector.begin() 结果类型不可转换为 const_iterator
【发布时间】:2014-09-30 20:06:01
【问题描述】:

容器需要提供iterator 类型,该类型可以隐式转换为const_iterator。鉴于此,我尝试使用auto 通过vector::begin() 初始化对象,并在std::distance 中使用生成的对象,其中RHS 是const_iterator。这是行不通的。这是一个完整的例子:

#include <cstdlib>
#include <vector>
#include <iterator>
#include <iostream>

typedef std::vector <char> Packet;
typedef std::vector <Packet> Packets;

template <typename Iter> 
Iter next_upto (Iter begin, Iter end, size_t n)
{
    Iter ret = begin;
      for (; n > 0 && ret != end; ++ret, --n)
            ;   
        return ret;
}

Packets::const_iterator Process (Packets::const_iterator begin, Packets::const_iterator end)
{
  Packets::const_iterator ret = begin;
  while (ret != end)
    ++ret;  // do something
  return ret;
}

int main()
{
  Packets test (100); // vector of 100 default-initialized packets

  // process them 10 at a time
  for (auto it = test.begin();
    it != test.end();
    it = next_upto (it, test.end(), 10))
  {
     auto itr = Process (it, next_upto (it, test.end(), 10));
     Packets::const_iterator it2 = it; 
     const size_t n1 = std::distance (it2, itr);
     const size_t n = std::distance (it, itr);
     std::cout << "Processed " << n << " packets\n";
  }
}

在 g++ 4.8.1(和 4.8.2)下编译会产生:

[1/2] Building CXX object CMakeFiles/hacks.dir/main.o
FAILED: /usr/bin/g++    -Wall -std=c++11 -g -MMD -MT CMakeFiles/hacks.dir/main.o -MF "CMakeFiles/hacks.dir/main.o.d" -o CMakeFiles/hacks.dir/main.o -c main.cpp
main.cpp: In function ‘int main()’:
main.cpp:39:45: error: no matching function for call to ‘distance(__gnu_cxx::__normal_iterator<std::vector<char>*, std::vector<std::vector<char> > >&, __gnu_cxx::__normal_iterator<const std::vector<char>*, std::vector<std::vector<char> > >&)’
      const size_t n = std::distance (it, itr);
                                             ^
main.cpp:39:45: note: candidate is:
In file included from /usr/include/c++/4.8/bits/stl_algobase.h:66:0,
                 from /usr/include/c++/4.8/vector:60,
                 from main.cpp:2:
/usr/include/c++/4.8/bits/stl_iterator_base_funcs.h:114:5: note: template<class _InputIterator> typename std::iterator_traits<_Iterator>::difference_type std::distance(_InputIterator, _InputIterator)
     distance(_InputIterator __first, _InputIterator __last)
     ^
/usr/include/c++/4.8/bits/stl_iterator_base_funcs.h:114:5: note:   template argument deduction/substitution failed:
main.cpp:39:45: note:   deduced conflicting types for parameter ‘_InputIterator’ (‘__gnu_cxx::__normal_iterator<std::vector<char>*, std::vector<std::vector<char> > >’ and ‘__gnu_cxx::__normal_iterator<const std::vector<char>*, std::vector<std::vector<char> > >’)
      const size_t n = std::distance (it, itr);
                                             ^

我知道我可以通过调用 cbegin()cend() 而不是 begin()end() 来修复这个特定实例,但是由于 begin()end() 返回一个应该可以转换为的类型const_iterator,我不确定我是否理解为什么需要这样做。

为什么auto 在这种情况下会推导出一个不可转换为const_iterator 的类型?

【问题讨论】:

  • 我们不能将此代码缩减为:gist.github.com/sharth/fbe0cddbe9a200d82b89
  • 它是,但你不投它。没有进行隐式转换来尝试匹配重载
  • @sp2danny:我想这就是我的答案。
  • 在您输入问题的标题之前,您是否没有注意到编译器在下一行将auto it = foo.begin() 转换为const_iterator 并且没有抱怨它?
  • 在不需要修改时考虑使用cbegincend而不是beginend

标签: c++ c++11 auto implicit-conversion


【解决方案1】:

您的问题可以简化为以下示例,该示例因相同原因而失败。

#include <vector>
#include <iterator>
int main()
{
    std::vector<int> v;
    std::vector<int>::const_iterator it1 = v.begin();
    auto it2 = v.end();
    auto n = std::distance(it1, it2);
}

std::distance 是为两个参数使用相同的模板参数类型定义的,并且模板参数推导失败,因为您有 const_iteratoriterator

从函数调用推导模板参数时,不考虑用户定义的转换,并且由于在这种情况下两个参数具有不同的类型,并且都参与模板参数推导,因此推导失败。

§14.8.1/6 [temp.arg.explicit]

如果参数类型不包含参与模板参数推导的模板参数,将对函数参数执行隐式转换(第 4 条)以将其转换为相应函数参数的类型。

§14.8.2.1/4 [temp.over]

... [ 注意: 如 14.8.1 所述,如果参数不包含 template-parameters 参与模板参数推导。除了前面列表中描述的转换之外,还允许进行此类转换。 ——尾注 ]

您需要将iterator 转换为const_iterator,或将模板参数显式指定为std::distance

auto n = std::distance(it1, static_cast<decltype(it1)>(it2));

auto n = std::distance<decltype(it1)>(it1, it2);

当然,其他选项是不使用 auto 并在两种情况下显式指定迭代器类型,或者在需要确保类型为 @987654335 时使用 vector::cbegin()vector::cend() 成员函数@。

【讨论】:

  • 嗯。我想我认为扣除应该是自动的。从iteratorconst_iterator 的转换不是首先在这里使用auto 吗?无论如何 +1。
  • @JohnDibling 用户定义的转换不考虑模板参数推导,只能应用单个标准转换序列,这里不是这种情况。
  • 我认为是我问题的真正答案。
  • @JohnDibling 类型推导在重载决议之前完成,并且 not 考虑到大多数隐式转换。 (有一些,比如数组到指针,这些都被考虑在内。)过度使用auto 是获得此类错误的好方法(或者更糟糕的是,代码不能满足您的要求)。
  • @John 我更倾向于 use auto eveyrwhere 阵营,减少必须拼出迭代器类型的冗长是auto 的主要用例之一,恕我直言。你确实遇到了一些像这样的陷阱,但你很快就会学会避免它们。特别是在使用auto 时,我倾向于在我知道只需要const_iterators 的地方使用cbegin()cend()
【解决方案2】:

这个问题与迭代器转换没有任何共同点。编译器根本无法确定模板参数。写出来也是一样的

int x = 10;
long y = 20;

std::cout << std::max( x, y ) << std::endl;

虽然int 类型的对象可以隐式转换为long 类型的对象。

至于你的例子,你可以写

const size_t n = std::distance<std::vector<char>::const_iterator> (it, itr);

【讨论】:

  • 其实我想我会在这里完全放弃auto,因为它似乎并没有给我带来任何好处。
  • @John Dibling 如果你用一个表达式处理不同的类型,至少为了可读性,明确指定它们的类型而不是使用 auto。
  • 嗯,我一直同意这一点。我试图在这里变得“科学”,并且不允许我以前的假设和期望影响我对auto 的普遍适用性的判断,但似乎我仍然同意我的观点。
【解决方案3】:

为什么在这种情况下自动推导出一个 typ[e 不能转换为 const_iterator?

  • begin() 有两个可用的重载:

签名:

iterator begin();

const_iterator begin() const;

您将向量声明为Packets test (100);,它是非常量的。

如果您将其声明为 const,auto 类型推导将具有第二个 begin() 重载作为最佳(和唯一)匹配。

It compiles and run with this simple fix.

【讨论】:

  • 好的,虽然这可能适用于这里,但它不适用于我的实际用例,即Packet 不是const.
  • @JohnDibling 我明白了,在那种情况下,Praetorian 的答案很合适,我想回答你的最后一个问题。
猜你喜欢
  • 2013-08-24
  • 1970-01-01
  • 1970-01-01
  • 2019-09-30
  • 2011-10-07
  • 2021-01-15
  • 2018-11-15
  • 1970-01-01
  • 2020-01-19
相关资源
最近更新 更多