如何编写一个可以接受数组或向量的函数?

How to write a function that can take in an array or a vector?

提问人:24n8 提问时间:11/12/2018 最后编辑:JeJo24n8 更新时间:7/4/2022 访问量:4789

问:

我想编写一个带有一个参数的 C++ 函数,以便可以传入以下任何一种类型:

std::vector<int>
std::array<int>
int array[numElements]
int *ptr = new int[numElements]
etc

模板化是实现这一目标的最佳方式吗?

C++ 数组函数 模板 stdvector

评论

0赞 L.C. 11/12/2018
HolyBlackCat 建议是你能做的最好的,而不会在兔子洞里太深......
9赞 Aconcagua 11/12/2018
类似 STL 的方式是模板化函数,接受开始和结束迭代器。
0赞 πάντα ῥεῖ 11/12/2018
@Iamanon这很大程度上取决于你希望在函数中如何处理这些类型,以及你计划使用哪些操作。你能为你的用例提供一个伪代码示例吗?
0赞 user657267 11/12/2018
至少在标准化之前,老式的方式可以正常工作。std::span
3赞 PaulMcKenzie 11/12/2018
etc.“等”是什么意思?你能保证它只会留下来吗?或任何整数容器怎么样?按照建议,传递迭代器允许您指定满足算法的任何序列。这样可以防止您在需求发生变化或出现新的容器类型等时将自己逼入绝境。std::vectorstd::arrayint *std::deque

答:

14赞 asu 11/12/2018 #1

如果你期望只是能够做到,你就不能,因为我无法想到你的函数可以推断出动态分配的大小。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 的更改,这也可以通过直接传递原始数组而不是作为两个指针来工作(但仍然不适用于动态分配的数组)。

评论

3赞 txtechhelp 11/12/2018
小提示:在你的正向迭代器中,如果你正在处理指针类型(如迭代器),则不需要 OR;相反,您可以只递增第一个指针并尊重它,例如:..这确实假设您使用指针类型。funcfor_eachauto vtemplate<class FWIt> void func(const FWIt a, const FWIt b) { while (a != b) { std::cout << *a; ++a; } }
0赞 asu 11/12/2018
不过,这行不通,问题似乎集中在支持尽可能多的容器类型上 - 所以假设我们正在处理前向迭代器似乎足够好 IMO。std::list
1赞 Daniel H 11/12/2018
实现 one-argument 的更好方法是 .这样,如果 和 是容器命名空间中的免费函数,则它有效,并且 and 适用于原始数组(可以通过引用接受)以及调用 和 的类型。funcusing std::begin; using std::end; func(begin(container), end(container));beginendstd::beginstd::end.begin.end
1赞 Daniel H 11/12/2018
@Asu 它将完美地工作。所有迭代器都可以递增。您的示例函数甚至可以使用标准循环适用于 InputIterator,而不仅仅是 ForwardIterator事实上,可以使用 for 循环来定义std::listforstd::for_eachtemplate<class InputIt, class UnaryFunction> constexpr UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f) { for (; first != last; ++first) { f(*first); } return f; }
1赞 Aconcagua 11/12/2018
'将与以下内容一起使用'“:远未完成:有序/无序集合、字符串、(非)有序映射(如果作为数据类型处理)、deques、...('[...] 还有很多:')。std::pair
3赞 JeJo 11/12/2018 #2

不能将所有列出的类型都获取到一个函数模板中。但是,您可以有函数模板重载,这将解决 和 的问题。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;
}

但是,对于动态分配的数组,您需要按照用户建议@Asu使用。


编辑:删除了冗余重载。

template<typename T, std::size_t N> void funArray(const T (&arr)[N]) {}

正如 @Daniel H 所指出的,上述 C 型数组的函数模板重载是徒劳的,因为它可以通过直接推导到 C 型数组来由第二个 overload() 处理。template<typename Container>

评论

1赞 Daniel H 11/12/2018
你不需要 ;如果边界已知,编译器将在第二个重载中推断出适当的数组类型。此外,如果您使用然后只是调用并且没有指定命名空间,则如果并且是容器命名空间中的免费函数,它将起作用;我知道这是标准建议,我认为它适用于类似的功能,但我不太确定。funArrayTusing std::begin; using std::endbegin()end()begin()end()swap
1赞 Daniel H 11/12/2018
如果您没有第三个重载并且仍然调用 ,它将推导为 ,而不是 ,并且等价于第三个重载。由于不能只在指针上工作,你可以通过注释掉第三个重载并查看它仍然按原样编译来检查这一点。funArray(array)Containerint[5]int*std::endmain
0赞 JeJo 11/12/2018
@DanielH感谢您指出这一点。我已经更新了。
4赞 einpoklum 11/12/2018 #3

模板化是实现这一目标的最佳方式吗?

这要视情况而定。如果你正在编写一个位于标头中的函数,因此可以在以后用于进一步的编译,那么 - 是的,可能:

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);

它使用跨度。如果您还没有听说过它们,请阅读:

什么是“跨度”,何时应该使用?

这个函数将能够按原样获取几乎所有的变量:、 和 普通(大小)数组可以在没有额外语法的情况下传递。但是,对于指针,您需要调用类似 .std::vectorstd::arrayf(gsl::make_span{ptr, numElements})

PS - 第三种选择,在标准库中非常常见,是将交互器而不是容器作为参数。这也需要模板化,因此它类似于第一个选项。

7赞 Bo R 11/12/2018 #4

span似乎就是你要找的。要么等待 C++20 :-)或使用 GSL 中的 span。请参阅什么是“跨度”以及何时应该使用跨度? 。示例如下。

你没有说你是否想要一个参数,该参数采用indata的可写或只读版本,因此该示例显示了两者。

#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;
}
0赞 Galik 11/12/2018 #5

如果它们都使用,那么您可以简单地接受开始和结束指针。您可以对它们使用标准算法,因为指针迭代器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);

传递两个迭代器(泛型或其他迭代器)的好处是,您不必将整个容器传递给算法。