我正在将 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 代码,它适合我的目的,谢谢大家。
这是一个状态机,可重现该生成器的功能。
#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";
}
}
将带有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
类型。
从 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);
}
};
我喜欢 Arthur Tacca 的解决方案,但只有两条评论:
其一,我不会声明“getNext()”方法,而是定义 () 运算符:
std::string operator()() {
switch (m_state) {
case STATE_START:
...
所以用法如下:
int main() {
auto generator = FileStructIterator("nomore.txt");
for (int i = 0; i < 10; ++i) {
std::string nextValue = generator();
std::cout << nextValue << "\n";
}
}
还有两个:如果只需要一个函数而不是 C++ 对象,我们总是可以将其封装在 lambda 中:
int main() {
auto generator = [](){ static FileStructIterator localGenerator("nomore.txt"); return localGenerator();};
for (int i = 0; i < 10; ++i) {
std::string nextValue = generator();
std::cout << nextValue << "\n";
}
}