【问题标题】:Why can template parameter be deduced only in one case?为什么只能在一种情况下推导出模板参数?
【发布时间】:2012-08-06 08:44:52
【问题描述】:

为什么下面的代码使用 non-const 迭代器 编译该行但 const 迭代器 编译失败(使用 Visual Studio 2008)?

#include <vector>

using std::vector;

int main(int argc, char* argv[])
{
    vector<int> test;

    test.push_back(1);
    test.push_back(2);

    vector<int>::const_iterator cit = test.end();
    std::distance(test.begin(), cit); // error: template parameter is ambiguous

    vector<int>::iterator it = test.end();
    std::distance(test.begin(), it);

    return 0;
}

注意:在 Visual Studio 2008 中,没有向量成员函数 cbegin() 来避免歧义,而是重载了 begin() 方法:

iterator begin()
    {   // return iterator for beginning of mutable sequence
    return (iterator(_Myfirst, this));
    }

const_iterator begin() const
    {   // return iterator for beginning of nonmutable sequence
    return (const_iterator(_Myfirst, this));
    }

【问题讨论】:

  • 根据经验,您会发现在大多数情况下您需要使用iterator 而不是const_iterator - 有关完整说明,请参阅Effective STL #26。

标签: c++ stl


【解决方案1】:

模板distance 接受一个参数distance&lt;T&gt;(T first, T last)。由于test.begin() 的类型为iteratorcit 的类型为const_iterator,因此无法推导出模板参数。

您可以使用test.cbegin() 来获得有保证的const_iterator,或者说static_cast&lt;std::vector&lt;int&gt; const &amp;&gt;(test).begin()

(这就像你有 max&lt;T&gt;(T x, T y) 并尝试说 max(1, 2U) 一样——它不会编译,因为它不明确。)

【讨论】:

  • 看我的笔记:Visual Studio 2008 向量实现中没有 cbegin()。
  • 我一直认为添加/删除常量是保留给const_cast,而不是static_cast
  • @TadeuszKopec:只有删除 CV 限定符需要const_cast。添加 constness 是完全允许的转换。
  • @KerrekSB 从技术上讲是的。但是const_cast 仍然更胜一筹,因为它确保不会发生其他类型的更改并清楚地表明您的意图。
  • @TadeuszKopec:实际上,由于转换是隐式的,implicit_cast 是最好的。请参阅a comment I made to this answer 中的示例实现。或者,可以编写一个简单的make_const 函数模板来对其参数进行 const 限定。 const_cast 很危险,只能用于添加 cv-qualification。
【解决方案2】:

注意:在 Visual Studio 2008 中,没有向量成员函数 cbegin() 来避免歧义,而是重载了 begin() 方法:

我认为编译器总是为非常量对象选择非常量重载,而只为常量对象选择常量方法。

打电话

std::distance(test.begin(), cit);

查看begin 的重载集和cit 的类型,并确定它是否可以匹配。它首先解决了重载(到非常量版本),因此失败了。

表达意图的最简洁的方式可能也适用于编译器:

vector<int> const &cref = test;
vector<int>::const_iterator cit = cref.end();
std::distance(cref.begin(), cit);

【讨论】:

  • 您的调用示例应该是std::distance(test.begin(), cit) 以匹配我的示例,不是吗?但是很好的解释,谢谢你的帮助。
  • 我故意在 const 引用上调用 begin,以强制选择 const 重载
【解决方案3】:

函数模板std::distance只需要一个模板 参数,两个函数参数必须相同。这 test.begin() 的返回类型只是 iterator,而不是 const_iterator,所以函数参数的类型不同。 编译器首先推断出std::vector&lt;int&gt;::iterator,然后 std::vector&lt;int&gt;::const_iterator为第二个,所以扣分 失败。模板参数推导没有考虑 可能的转换,除了最简单的转换。

有很多方法可以解决问题,但它们都有明显的 缺点。 (这可能就是委员会添加cbegincend 函数。)目前,您最好的选择可能就是放弃 const,而同时使用 iterator

【讨论】:

  • 我很困惑:begin() 的返回类型是 either const_iterator or 迭代器(请参阅我在问题中的注释)。所以两个参数的类型不一定不同。
  • @nabulke begin 在 const 对象上调用返回 const_iterator。在非 const 上返回 iterator
  • @TadeuszKopec:啊,谢谢你的评论!这是帮助我理解这个问题的缺失链接。
【解决方案4】:

distance 必须具有相同时间的模板参数。在这种情况下,它是_Vector_const_iteratorbegin 有两个重载:const 和 non-const。 const-version 产生_Vector_const_iterator,但另一个产生_Vector_iterator_Vector_iterator 继承自 _Vector_const_iterator

因此,begin 的 const 和非 const 重载都能够生成 _Vector_const_iterator,并且它们都可以被选为非常量对象,即编译器会感到困惑。

这就是我编译它的方式:

    std::distance(((const vector<int>&)test).begin(), cit);

【讨论】:

  • 但是为什么编译器不会对 both 示例感到困惑?
  • 因为如果你对距离的第二个参数使用非常量迭代器,那么编译器只能选择 begin() 的非常量版本
猜你喜欢
  • 2017-05-20
  • 2012-09-15
  • 2020-11-29
  • 2017-02-20
  • 2013-04-30
  • 2018-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多