【问题标题】:C++: How to pass any iterable type as a function parameterC++:如何将任何可迭代类型作为函数参数传递
【发布时间】:2017-03-31 13:42:29
【问题描述】:

作为一个练习,我尝试在 C++ 中实现 Python 的 str.join 方法。我最终会将该函数添加为std::string 类的方法,但我认为让它工作更重要。我将函数定义如下:

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

有什么方法可以确保 Iterable 类型实际上是可迭代的?例如。我不想收到intchar..

【问题讨论】:

  • 你如何定义“可迭代”?
  • @Galik 我会将其定义为可以迭代的容器
  • 你能提供一个真实的例子来说明你将如何调用这个函数吗?
  • 那么您的iterable 是一个可迭代容器,而不是迭代器?
  • 是的。请原谅,我正在努力提高我的 C++ 技能

标签: c++ iterable generic-type-argument


【解决方案1】:

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

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

请注意,sep 应作为 const reference 传递,因为您不需要复制它。

不过,您不必担心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* 才能工作,这是一个很好理解的迭代器契约。

【讨论】:

  • "(几乎是一个指针)" .. 从技术上讲,它 可能 是一个指针,例如char* x = "hello"; join("", x, x+5);
  • 另外,你能解释一下为什么这是一个糟糕的实现吗?
  • “我们传递一个迭代器而不是一个Iterable - 好吧,我们不必这样做。当 Stepanov 决定采用这个时,他使 stl 算法变得非常冗长。 Andrei Alexandrescu 谈到了基于 Range 的算法相对于基于 Iterator 的算法的优点。
  • @aydow 这是一个糟糕的实现,因为它效率低下。它不是非常低效,所以它可以在很多情况下工作,但我没有尝试优化它。例如,注意我们如何在每个循环中检查begin != end 两次;可能可以改组代码以改善这一点。此外,虽然 Python 只要求迭代器中的参数是字符串,但可以让代码适用于任何可以转换为字符串的类型(可能通过std::to_string)。有更多选择;我说这很糟糕,因为我只是扔掉了示例代码。
  • 我不同意(并且我不想进行扩展讨论)。我非常尊重标准库,但在编写自己的新实用程序时,我会遵循我认为更好的方法。
【解决方案2】:

所有标准 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
}

[live demo]

【讨论】:

  • 为什么禁止“不使用 sep”?肯定会使用 sep。
【解决方案3】:

您可以使用模板模板语法 - 如果需要 - 使用 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

【讨论】:

    【解决方案4】:

    来自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) { ... }
    }
    

    但请参阅该博客文章中的 cmets 以获得更多改进。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-31
      • 2013-12-13
      • 1970-01-01
      • 2018-10-30
      • 1970-01-01
      • 1970-01-01
      • 2011-04-28
      • 1970-01-01
      相关资源
      最近更新 更多