我希望编写一个只有一个参数的C++函数,可以传入以下任一类型:
std::vector<int>
std::array<int>
int array[numElements]
int *ptr = new int[numElements]
etc
模板化是否是实现这个目标的最佳方法?
func(v)
,那是不可能的,因为我想不到任何方法使你的函数可以推断出动态分配的int[numElements]
的大小。
一种好的方法是使用一对前置迭代器,也就是说,如果你只需要逐一迭代项,因为在某些容器上(如std::list
)随机访问非常糟糕。template<class FWIt>
void func(FWIt a, const FWIt b)
{
while (a != b)
{
std::cout << "Value: " << *a << '\n';
++a;
}
}
template<class T>
void func(const T& container)
{
using std::begin;
using std::end;
func(begin(container), end(container));
}
以下内容可以正常运行:
int array[5] = {1, 2, 3, 4, 5};
func(array);
int* dynarray = new int[5]{1, 2, 3, 4, 5};
func(dynarray, dynarray + 5);
std::vector<int> vec{1, 2, 3, 4, 5};
func(vec);
func(vec.begin(), vec.end());
std::list<int> list{1, 2, 3, 4, 5};
func(list);
编辑:由于@DanielH的更改,现在也可以直接传递原始数组而不是两个指针,但仍无法使用动态分配的数组。
template<class FWIt> void func(const FWIt a, const FWIt b) { while (a != b) { std::cout << *a; ++a; } }
,但需要注意的是,这仅假设您仅使用指针类型。 - txtechhelpfunc
的方法是 using std::begin; using std::end; func(begin(container), end(container));
。这样,如果 begin
和 end
是容器命名空间中的自由函数,则可以正常工作,并且 std::begin
和 std::end
可以用于原始数组(可以通过引用接受)和调用 .begin
和 .end
的类型。 - Daniel Hstd::list
它将完美地工作。所有迭代器都可以被递增。你的示例函数甚至可以使用标准的 for
循环来处理 InputIterator,而不仅仅是 ForwardIterator。事实上,std::for_each
可以使用 for
循环定义,使用 template<class InputIt, class UnaryFunction> constexpr UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f) { for (; first != last; ++first) { f(*first); } return f; }
- Daniel Hstd::pair
作为数据类型),双端队列等等[...]和许多其他的。 - Aconcaguaspan
似乎是你正在寻找的。要么等待 C++20 :-) 或使用 GSL 中的 span。请参见什么是“span”,何时应该使用它?
下面是一个示例。
你没有说明想要一个接受可写或只读版本的输入数据的参数,因此示例同时展示了两者。
#include <array>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <vector>
#if defined(__has_include) && __has_include(<span>)
#include <span>
#endif
#ifdef __cpp_lib_span
using std::span;
#else
#include <gsl/gsl>
using gsl::span;
#endif
void funcDoSomething(span<int> data) {
int value{41};
for (auto &i: data) {
i += value;
++value;
}
}
void funcReadOnly(span<int const> data) {
for (auto &i: data) {
// ++i; won't compile since we have read-only span values
std::cout << i << ' ';
}
std::cout << '\n';
}
int main() {
std::vector<int> stdvec{10, 11, 12};
funcDoSomething(stdvec);
funcReadOnly(stdvec);
std::array<int, 3> stdarr{20, 21, 22};
funcDoSomething(stdarr);
funcReadOnly(stdarr);
int carr[3]{30, 31, 32};
funcDoSomething(carr);
funcReadOnly(carr);
auto ptr = std::unique_ptr<int[]>(new int[3]);
ptr[0] = 40;
ptr[1] = 41;
ptr[2] = 42;
funcDoSomething({ptr.get(), 3});
funcReadOnly({ptr.get(), 3});
return EXIT_SUCCESS;
}
使用模板化技术是实现这一目标的最佳方式吗?
这要看情况。如果您正在编写一个将用于进一步编译的头文件中的函数,那么最好使用模板化技术:
template <typename IntContainer>
void f(Container& c);
template <typename IntContainer>
void f(const Container& c);
void f(gsl::span<int> sp);
或者
void f(gsl::span<const int> sp);
该函数使用了一个span。如果您还不知道它是什么,请阅读:
这个函数几乎可以直接使用你的所有变量:标准库容器std::vector
,std::array
和定长数组可以直接传递,只有指针需要像这样调用:f(gsl::make_span{ptr, numElements})
。
PS-标准库中非常常见的第三种选择是将迭代器而不是容器作为参数。这也需要进行模板化,因此与第一种选项类似。
std::vector<>
, std::array<>
和 Type array[numElements]
的问题。template<typename Iter>
void funArray(const Iter begin, const Iter end)
{
std::cout << "Actual code here\n";
}
template<typename Container> void funArray(const Container& arr)
{
funArray(std::begin(arr), std::end(arr)); //std::vector or std::array
}
int main()
{
const std::size_t numElements = 5;
std::vector<int> vec;
std::array<int, numElements> arr;
int array[numElements];
int *ptr = new int[numElements];
funArray(vec);
funArray(arr);
funArray(array);
funArray(ptr, ptr+numElements);
return 0;
}
template<typename T, std::size_t N> void funArray(const T (&arr)[N]) {}
正如@Daniel H所指出的那样,上述用于C类型数组的函数模板重载是徒劳的,因为它可以通过第二个重载(template<typename Container>
)直接推导到C类型数组。
funArray
的第三个重载;如果边界已知,编译器将在第二个重载中推断出 T
适当的数组类型。此外,如果你使用 using std::begin; using std::end
,然后调用 begin()
和 end()
而不指定命名空间,如果 begin()
和 end()
是容器命名空间中的自由函数,则会起作用;我知道这是 swap
的标准建议,我认为类似函数也是如此,但我不太确定。 - Daniel HfunArray(array)
,它将将Container
推断为int [5]
而不是int *
,并等同于第三个重载。由于std :: end
不能在指针上工作,因此您可以通过注释第三个重载并查看main
仍然可以编译来检查它。 - Daniel H如果它们都使用int
,那么你可以简单地接受开始和结束指针。你可以在它们上面使用标准的算法,因为指针就是迭代器。
void my_func(int const* begin, int const* end)
{
std::for_each(begin, end, [](int i){
std::cout << i << '\n';
});
}
然后:
std::vector<int> v;
std::array<int> a;
int array[numElements];
int* ptr = new int[numElements];
my_func(v.data(), v.data() + v.size());
my_func(a.data(), a.data() + a.size());
my_func(std::begin(array), std::end(array));
my_func(ptr, ptr + numElements);
std::span
标准化之前,老式的方法也能正常工作。 - user657267std::vector
、std::array
或int *
吗?那std::deque
或任何整数容器呢?建议使用迭代器,这样可以指定满足算法要求的任何序列,如果需求改变或出现新的容器类型,这将避免限制自己。 - PaulMcKenzie