将带有 yield(string) 的 python 函数翻译成 C++

translate python function with yield(string) to C++

我正在将 python 函数转换为 C++ 函数;这个函数使用了一个我不知道如何翻译的yield(string)语句。

这里是整个故事......我有一个特定的函数,它读取一个包含数据行的输入 ASCII 文件 filename,并将文件的内容放入 std::vector。当读取这个输入文件时(在另一个函数方面,如下所示),我需要跳转一堆行(5 或 6,这取决于输入文件的名称),为此,我定义了一个函数, file_struct_generator,它将数据线标记为“数据”,将我不需要的数据线标记为“未使用”。我在 C++ 中需要类似于此函数的东西,特别是,我需要类似于 yield(string) 的东西(注意,string!),但当然是在 C++ 中。在这里,我向您展示了我需要从 python“翻译”到 C++ 的代码行。如何在 C++ 中重写 yield("unused")yield("data")?或者如果 yield 在 C++ 中不可用,我可以在 C++ 中使用不同的东西作为生成器编写类似的函数吗?谢谢你帮助我!

def file_struct_generator(filename):
    if "more" in filename:
        bad_line_number = 5
    else:
        bad_line_number = 6

    yield("data")
    for i in range(bad_line_number):
        yield("unused")
    while True:
        yield("data")
    

编辑: 我不使用 C++20,而是使用 C++11。我尝试了@Arthur Tacca 代码,它对我有用,谢谢大家。

translate python function with yield(string) to C++

在 C++20 之前,我认为没有简单、准确的翻译。

另一种方法是编写一个接受仿函数的函数模板:

template<class Yield>
void file_struct_generator(const std::string& filename, const Yield& yield)
{
    // ...
    yield("unused")
    // ...

通过这种方式,调用者可以提供一个仿函数,以他们希望使用的方式处理输出。例如,他们可以提供打印值的方法,或将值存储在容器中的方法。

重要的是,这个替代方案不是惰性的,您不能像 python 代码中那样出现无限循环。这是否是一个有用的翻译取决于生成器在 Python 中的使用方式。这对于消耗整个序列的情况很有用。


我认为这将是 C++20 中的直接翻译:

generator<std::string>
file_struct_generator(const std::string& filename)
{
    bad_line_number = filename.find("more") != std::string::npos
        ? 5
        : 6;

    co_yield "data";
    for (int i : std::views::iota(0, bad_line_number))
        co_yield "unused";
    for(;;)
        co_yield "data";
}

由于缺少符合标准的编译器,因此未经过测试。此外,该标准不提供 generator 类型。

从 C++20 开始,您可以使用 co-routines,您可以使用一些 thread_local 变量(静态)实现与 python 片段类似的功能:

std::string file_struct_generator(std::string filename) 
{
  thread_local int bad_line_number = filename.find("more") != std::string::npos ? 5 : 6;

  thread_local int i = 0;

  if (i++ && i <= bad_line_number) 
    return "unused";

  return "data";
}

请注意,正如 ArthurTacca 在评论中指出的那样,这个函数实际上不能用不同的 file-name 调用,或者至少,如果你这样做,它不会开始一个新的循环.

这是一个重现生成器功能的状态机。

#include <stdexcept>
#include <string>
#include <iostream>

class FileStructIterator {
public:
    explicit FileStructIterator(const std::string& filename): 
        m_filename(filename), m_state(STATE_START) 
    {}

    std::string getNext() {
        switch (m_state) {
        case STATE_START:
            m_state = STATE_LOOP1;
            if (m_filename.find("more") != std::string::npos) {
                m_badLineNumber = 5;
            } else {
                m_badLineNumber = 6;
            }
            m_i = 0;
            return "data";

        case STATE_LOOP1:
            ++m_i;
            if (m_i >= m_badLineNumber) {
                m_state = STATE_LOOP2;
            }            
            return "unused";

        case STATE_LOOP2:
            return "data";

        default:
            throw std::logic_error("bad state");
        }
    }

private:
    std::string m_filename;

    enum State { STATE_START, STATE_LOOP1, STATE_LOOP2 };
    State m_state;

    size_t m_badLineNumber;
    size_t m_i;
};

这是一个使用它的例子(在这种情况下我将输出限制为前 10 个结果,因此它不会永远循环)。

int main() {
    auto it = FileStructIterator("nomore.txt");
    for (int i = 0; i < 10; ++i) {
        std::string nextValue = it.getNext();
        std::cout << nextValue << "\n";
    }
}

在 C++20 中,这将使用 co_yield。在此之前你可以写一个迭代器。

class file_struct_iterator : public std::iterator<std::input_iterator_tag, std::string, std::ptrdiff_t, const std::string *, std::string> {
    enum class State {
        initial,
        bad_lines,
        remainder
    }

    State state = State::initial;
    size_t bad_line_number = 0;

public:

    file_struct_iterator(std::string filename) 
        : bad_line_number((filename.find("more) != filename.end()) ? 5 : 6)
    {}

    std::string operator*() {
        switch (state) {
            case State::initial:
            case State::remainder: return "data";
            case State::bad_lines: return "unused";
        }
    }

    file_struct_iterator& operator++() {
        switch (state) {
            case State::initial: state = State::bad_lines; break;
            case State::bad_lines: if (--bad_line_number == 0) { state = remainder; } break;
            case State::remainder: break;
        }
        return *this;
    }

    file_struct_iterator operator++(int) {
        file_struct_iterator retval = *this;
        ++(*this);
        return retval;
    }

    bool operator==(file_struct_iterator other) const {
        return (state == other.state) && (bad_line_number == other.bad_line_number);
    }
    
    bool operator!=(file_struct_iterator other) const {
        return !(*this == other);
    }
};