正确编写基于范围的构造函数

Correctly Writing a Range Based Constructor

本文关键字:范围 构造函数 于范围      更新时间:2023-10-16

我有一个关于为类编写基于范围的构造函数的问题,但找不到正确的措辞来搜索谷歌上的帮助。

假设我正在编写一个简单的类,如 vector,它涉及一个基于范围的构造函数,该构造函数将范围内的元素插入到当前容器中:

// foo.h
#ifndef FOO_H
#define FOO_H
#include <iostream>
class Foo {
public:
Foo() {
std::cout << "Default constructor called" << std::endl;
}
template<class InputIterator> Foo(InputIterator first, InputIterator last) {
std::cout << "Range based constructor called" << std::endl;
}
Foo(size_t n, int val) {
std::cout << "size_t, int constructor called" << std::endl;
}
};
#endif // FOO_H

并有一个 CPP 文件

#include <iostream>
#include <vector>
#include "foo.h"
using std::cout;    using std::endl;
int main() {
std::vector<int> v(10, 5);
Foo f_default;
Foo f_range(v.begin(), v.end());   
Foo f_int(314, 159);       // want this to call size_t, int ctctr
return 0;
}

在main的第三行中,我们创建了一个直观地想要调用size_t int构造函数的Foo f_int(314, 159)。但是,它与范围的通用模板构造函数匹配。有没有办法在C++解决这样的问题?我觉得我错误地处理了基于范围的构造函数。

我可以想象你可以使用模板专业化,但真的不知道如何。

可能发生这种情况的一个例子是,如果我们正在编写一个向量类,其中有一个基于size_t和默认值的构造函数(将在类上模板化,但我在这里进行了简化)和另一个基于迭代器范围的构造函数。

在第三种情况下,构造函数模板是更好的匹配,因为您要传递两个int参数,而Foo(size_t n, int val)需要int才能为第一个参数size_t转换。如果将代码修改为

Foo f_int(static_cast<size_t>(314), 159);

调用所需的构造函数。但是,当然,这不是一个好的解决方案,因为很容易意外调用错误的构造函数。相反,您可以使用 SFINAE 通过确保参数类型是迭代器,从重载解析集中删除构造函数模板。

template<class InputIterator,
class = std::enable_if_t<
std::is_base_of<
std::input_iterator_tag,
typename std::iterator_traits<InputIterator>::iterator_category
>{}
>
>
Foo(InputIterator first, InputIterator last) {
std::cout << "Range based constructor called" << std::endl;
}

如果您查看此处的表格,您可以从中读取的所有迭代器都是输入迭代器或派生类型。因此,上面的代码正在检查传递给构造函数的InputIterator类型是否属于该类型或从该类型派生的内容。