【问题标题】:Range based for loops on null terminated strings基于空终止字符串的for循环范围
【发布时间】:2013-01-23 10:35:42
【问题描述】:

我有点假设基于范围的 for 循环将支持 C 风格的字符串

void print_C_str(const char* str)
{
    for(char c : str)
    {
        cout << c;
    }
}

但事实并非如此,标准[stmt.ranged] (6.5.4) 表示基于范围的适用于以下三种可能性之一:

  1. 范围是一个数组
  2. 范围是具有可调用beginend 方法的类
  3. 在关联的命名空间(加上std 命名空间)中有可访问的 ADL

当我在全局命名空间中为 const char* 添加 beginend 函数时,我仍然会收到错误(来自 VS12 和 GCC 4.7)。

有没有办法让基于范围的 for 循环与 C 风格的字符串一起工作?

我尝试将重载添加到 namespace std,这很有效,但据我了解,将重载添加到 namespace std 是非法的(这是正确的吗?)

【问题讨论】:

  • 您可以合法地在 std 命名空间中专门化模板。
  • @bamboon true 但 IIRC 仅适用于用户定义的类型,这是重载而不是专门化,适用于内置类型,而不是 UDT。
  • 你为什么要传递 C 字符串?
  • 解决办法是不要把C字符串当成字符串。 std::string 已经够糟糕了,因为它缺少编码的概念。
  • @AlexChamberlain 我正在使用标准 C 字符串的原始 UDL

标签: c++ for-loop c++11 null-terminated


【解决方案1】:

如果您为空终止字符串编写一个简单的迭代器,您可以通过在返回特殊范围的指针上调用函数来做到这一点,而不是将指针本身视为范围。

template <typename Char>
struct null_terminated_range_iterator {
public:
    // make an end iterator
    null_terminated_range_iterator() : ptr(nullptr) {}
    // make a non-end iterator (well, unless you pass nullptr ;)
    null_terminated_range_iterator(Char* ptr) : ptr(ptr) {}

    // blah blah trivial iterator stuff that delegates to the ptr

    bool operator==(null_terminated_range_iterator const& that) const {
        // iterators are equal if they point to the same location
        return ptr == that.ptr
            // or if they are both end iterators
            || is_end() && that.is_end();
    }

private:
    bool is_end() {
        // end iterators can be created by the default ctor
        return !ptr
            // or by advancing until a null character
            || !*ptr;
    }

    Char* ptr;
}

template <typename Char>
using null_terminated_range = boost::iterator_range<null_terminated_range_iterator<Char>>;
// ... or any other class that aggregates two iterators
// to provide them as begin() and end()

// turn a pointer into a null-terminated range
template <typename Char>
null_terminated_range<Char> null_terminated_string(Char* str) {
    return null_terminated_range<Char>(str, {});
}

用法如下:

for(char c : null_terminated_string(str))
{
    cout << c;
}

我不认为这会失去任何表现力。其实我觉得这个比较清楚。

【讨论】:

  • +1,它更干净,因为它可以正确地与char*char[] 一起使用。 char[] 的问题在于它本身可以工作但做错了事情(它被视为任何 C 数组,而不是以零结尾的字符串,因此一个元素太长了。
  • +1 出于好奇,这个解决方案是否比for (char c: std::string(str)) 更受欢迎?对我来说,这似乎是一个显而易见的解决方案。由于没有人将其发布为答案,我只能猜测我缺少一些东西。在您发布或使用std::string 的此解决方案中,需要构造一些额外的东西来执行迭代。
  • @hmjd 是的,for (char c: std::string(str)) 是另一种选择。这种方法优于 std::string 的优点是这种抽象具有非常低的运行时成本:构造的额外对象非常便宜,不像 std::string 可能涉及动态分配并将整个字符串复制到自己的缓冲。基本上,虽然我不会嘲笑看到 std::string 用于此目的(除非分析证明它是一个性能问题),但这提供了所需的功能,迭代,而 std::string 提供了不需要的功能,这就是费用。
  • @R.MartinhoFernandes,性能方面是我能想到的唯一可能的原因。干杯。
  • @R.MartinhoFernandes:这非常漂亮,但我敢打赌,使用代理类定义 begin() 和 end() 的不太聪明的解决方案是 begin(s) 只是 @987654330 @ 和 end(s)s+strlen(s) 会变得同样快,如果不是更快的话。我的理论是基于我的怀疑,即 strlen() 已高度优化,不需要在每个字节上进行字节比较,因此执行此操作的开销应该大致等于执行两个测试(或可能三个)而不是一个测试的开销在每次循环迭代中。无论如何,它的工作量肯定会减少,因为它根本不需要自定义迭代器。
【解决方案2】:

一种可能的解决方法是将空终止的字符串包装在另一种类型中。最简单的实现如下(它的性能不如R. Martinho Fernandes 的建议,因为它调用strlen,但它的代码也少得多)。

class null_terminated_range {
    const char* p:
public:
    null_terminated_range(const char* p) : p(p) {}
    const char * begin() const { return p; }
    const char * end() const { return p + strlen(p); }
};

用法:

for(char c : null_terminated_range(str) ) 

【讨论】:

    【解决方案3】:

    C 字符串不是数组,它不是具有begin/end 成员的类,并且您不会通过 ADL 找到任何内容,因为参数是原语。可以说,这应该是简单的非限定查找,使用 ADL,在全局命名空间中找到一个函数。但是,鉴于措辞,我认为这是不可能的。

    【讨论】:

      猜你喜欢
      • 2016-10-31
      • 1970-01-01
      • 2014-12-06
      • 2014-01-12
      • 2013-01-04
      • 1970-01-01
      • 2021-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多