如何将任何可迭代类型作为函数参数传递

C++: How to pass any iterable type as a function parameter

本文关键字:函数 参数传递 类型 迭代 任何可      更新时间:2023-10-16

作为练习,我试图在c++中实现Python的str.join方法。我最终将添加函数作为std::string类的方法,但我认为让它工作更优先。我将函数定义如下:

template<typename Iterable>
std::string join(const std::string sep, Iterable iter);

是否有任何方法可以确保Iterable类型实际上是可迭代的?例:我不想收到intchar .

在c++中,我们没有一个Iterable,而是传递一个迭代器(几乎是指针)到范围的前面和末尾:

template<typename Iter>
std::string join(const std::string &sep, Iter begin, Iter end);

注意,sep应该作为const 引用传递,因为您不需要复制它。

您不需要担心Iter是否实际上是一个迭代器。这是因为如果不工作,代码将无法编译。

例如,假设您这样实现它(这是一个糟糕的实现):

template<typename Iter>
std::string join(const std::string &sep, Iter begin, Iter end) {
    std::string result;
    while (begin != end) {
        result += *begin;
        ++begin;
        if (begin != end) result += sep;
    }
    return result;
}

那么作为Iter传入的类型必须有一个operator++、一个operator!=和一个operator*才能工作,这是很容易理解的迭代器契约

所有标准c++集合都有begin()end()成员函数。您可以利用这一事实来确保传递的参数实际上是某些SFINAE (c++11示例)的集合(用您的术语来说是可迭代的):

#include <array>
#include <list>
#include <vector>
#include <map>
#include <string>
template <class Iterable>
auto join(const std::string sep, const Iterable& iterable) -> decltype(iterable.begin(), iterable.end(), std::string{}) {
    (void)sep; // to suppress warning that sep isn't used
    // some implementation
    return {};
}
int main() {
    join(";", std::array<int, 5>{});
    join(";", std::list<int>{});
    join(";", std::vector<float>{});
    join(";", std::string{});
    join(";", std::map<int, float>{});
    //join(";", int{}); // does not compile as int is not a collection
}
(现场演示)

您可以使用模板模板语法,如果需要,还可以使用SFINAE来确保存在适当的类成员:

#include <vector>
#include <list>
#include <string>
#include <map>
#include <ostream>
//! Single value containers.
template< template<class> class L, class T,
    class EntryT = typename L<T>::value_type>
std::string my_join(const std::string_view sep, const L<T>& anyTypeIterable)
{
    std::stringstream ss;
    bool first = true;
    for (const EntryT& entry : anyTypeIterable)
    {
        if (first) first = false;
        else ss << sep;
        ss << entry;
    }
    return ss.str();
}
//! std::map specialization - SFINAE used here to filter containers with pair value_type
template< template<class, class> class L, class T0, class T1,
    class EntryT = typename L<T0, T1>::value_type,
    class FirstT = typeof(EntryT::first),
    class SecondT = typeof(EntryT::second)>
std::string my_join(const std::string_view sep, const L<T0, T1>& anyTypeIterable)
{
    std::stringstream ss;
    bool first = true;
    for (const EntryT& entry : anyTypeIterable)
    {
        if (first) first = false;
        else ss << sep;
        ss << entry.first << sep << entry.second;
    }
    return ss.str();
}
int main()
{
    std::cout << my_join("; ", std::vector<int>({1, 2, 3, 4})) << std::endl;
    std::cout << my_join("; ", std::list<int>({1, 2, 3, 4})) << std::endl;
    std::cout << my_join("; ", std::string("1234")) << std::endl;
    std::cout << my_join("; ", std::map<int, int>({ {1, 2}, {3, 4} })) << std::endl;
    return 0;
}
// Output:
// 1; 2; 3; 4
// 1; 2; 3; 4
// 1; 2; 3; 4
// 1; 2; 3; 4

From https://devblogs.microsoft.com/oldnewthing/20190619-00/?p=102599:

template<typename C, typename T = typename C::value_type>
auto do_something_with(C const& container)
{
  for (int v : container) { ... }
}

或者如果容器没有实现value_type:

template<typename C, typename T = std::decay_t<decltype(*begin(std::declval<C>()))>>
auto do_something_with(C const& container)
{
  for (int v : container) { ... }
}

或者如果您只想要包含可转换为int类型的容器:

template<typename C, typename T = std::decay_t<decltype(*begin(std::declval<C>()))>,
    typename = std::enable_if_t<std::is_convertible_v<T, int>>>
auto do_something_with(C const& container)
{
  for (int v : container) { ... }
}