编译器在将 std::vector::begin() 传递给 std::vector::insert 时如何推断要调用哪个版本的 std::vector::begin()?

How does the compiler deduce which version of std::vector::begin() to call when passing it to std::vector::insert?

提问人:ulak blade 提问时间:8/5/2022 更新时间:8/5/2022 访问量:81

问:

我正在尝试制作自己的微型向量类,并且正在尝试复制一些函数,但是在传递调用(例如参数)和参数时,我无法让它们以相同的方式运行 - 编译器没有推断出正确的版本。下面是一个示例:begin()end()

template<typename T>
class Iterator
{
public:
Iterator() {}
};

template<typename T>
class ConstIterator
{
public:
ConstIterator() {}
};

template <typename T>
class MyList {
   public:
    MyList() {}

    Iterator<T> Begin()
    {
        return Iterator<T>();
    }

    ConstIterator<T> Begin() const
    {
        return Iterator<T>();
    }

    void Insert(ConstIterator<T> it) 
    {

    }
};


int main() {
    MyList<int> myList;

    myList.Insert(myList.Begin());
}

在它没有尝试使用正确的 ,const 版本。myList.Insert(myList.Begin());Begin()

从我在 std::vector 实现中可以看出,begin() 有两个版本 - 一个返回一个,一个返回一个 .它们之间唯一的其他区别是,一个是 const 方法(返回 const_iterator 的方法)。iteratorconst_iterator

_NODISCARD _CONSTEXPR20 iterator begin() noexcept {
    auto& _My_data = _Mypair._Myval2;
    return iterator(_My_data._Myfirst, _STD addressof(_My_data));
}

_NODISCARD _CONSTEXPR20 const_iterator begin() const noexcept {
    auto& _My_data = _Mypair._Myval2;
    return const_iterator(_My_data._Myfirst, _STD addressof(_My_data));
}

许多方法(如 std::vector::insert)都采用const_iterator参数:

_CONSTEXPR20 iterator insert(const_iterator _Where, const _Ty& _Val) { // insert _Val at _Where
    return emplace(_Where, _Val);
}

_CONSTEXPR20 iterator insert(const_iterator _Where, _Ty&& _Val) { // insert by moving _Val at _Where
    return emplace(_Where, _STD move(_Val));
}

但是,insert 方法中没有任何内容可以使编译器使用 begin() 的常量版本。 这意味着它必须仅通过返回类型来推断,但据我所知这是不可能的? 那么它是如何实现的呢?

C++ 模板 STL 容器 STDVECTOR

评论

0赞 Alan Birtles 8/5/2022
std::vector只是为每个迭代器有一个单独的重载 en.cppreference.com/w/cpp/container/vector/insert
0赞 user17732522 8/5/2022
@AlanBirtles“(直到 C++11)”。

答:

6赞 user17732522 8/5/2022 #1

没有扣除。如果 不是 -qualified,则需要 的非版本。否则,将调用版本。你如何使用的结果无关紧要。myListconstconstBegin()myList.Begin()constmyList.Begin()

标准库通过提供从非常量迭代器到常量迭代器的转换来避免您的问题。例如,你可以给出一个构造函数,它接受一个 ,无论如何你都必须拥有它才能在你的工作版本中发表声明(假设这不是一个错别字)。ConstIteratorIteratorreturn Iterator<T>();constBegin()

评论

0赞 ulak blade 8/5/2022
你是对的,它需要一个转换,但是显式转换构造函数在很多地方都会引起歧义,因为现在它既可以是一个常量迭代器,也可以是一个常规迭代器,所以一些模板方法采用多个 T 类型的参数,这些参数应该是迭代器,无法推断出它应该是什么类型的迭代器。我正在查看 stl 实现,看看他们究竟是如何进行转换的。似乎他们的迭代器继承了const_iterator,但我还没有看到转换构造函数或运算符。
0赞 user17732522 8/5/2022
@ulakblade 您可能使用了错误的术语,但构造函数不能是 。如果是,则不能将其用于此处预期的隐式转换。迭代器之间不存在歧义问题。首先,您不需要在两个迭代器上重载函数,如果函数不修改引用值,则仅选择常量迭代器,否则选择非常量迭代器。但是,即使您同时拥有两者,也不需要转换的重载将是首选。explicitexplicit
0赞 user17732522 8/5/2022
对于模板化函数,这并不重要,因为它们应该直接将迭代器类型作为模板参数,以便它始终与参数中给出的迭代器类型完全匹配。无论如何,对推导类型进行隐式转换是不可能的。
0赞 user17732522 8/5/2022
@ulakblade 您正在关注哪个标准库实现?
0赞 ulak blade 8/5/2022
是的,对不起,我的意思是隐含的。我对歧义的意思是这样的 - godbolt.org/z/6G19asYsb Distance 函数 - 传递给它的参数是模棱两可的,即使它可以从一个迭代器类型转换为另一个迭代器类型