将带有yieldstring的python函数转换为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代码,它可用于我的目的,谢谢大家。

解决方法

将带有yield(string)的python函数转换为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类型。

,

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

#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开始,您可以使用协例程,并且可以通过一些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在评论中指出的那样,实际上不能使用其他文件名调用此函数,或者至少,如果执行此操作,它将不会启动新循环。

,

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