在C++17/20中迭代元组

4

有没有人知道在C++17/20中遍历元组的好方法?假设我们有这样一段代码:

class Test
{
    public:
        Test( int x ) : x_(x) {};
        void Go() const { std::cout << "Hi!" << x_ << "\n" ; }
        int x_;
};
int main()
{
    std::tuple tplb{ Test{1} , Test{2} ,  Test{3} };
}

我们如何迭代遍历元组,并使用最新的17/20特性在每个元素上调用Go()方法?
我知道您可以只需具有对象向量,然后就可以轻松工作。我希望通过这种方式能够实现某种多态性,而无需使用虚函数。
这个想法是能够在元组中拥有其他支持相同方法的对象类型。如果每个对象都存在该方法,则代码将会编译并执行,而不必使用基类、虚函数、vtable等。
也许可以使用std::applystd::invoke来实现吗?

1
@mistertribs 那个问题询问的是 C++11,所以现在已经过时了。 - Brian Bi
@Brian - 尽管如此,那里的一些答案适用于C++17。 - user9723177
是的,我认为/希望在17/21中有一些新的结构可以使这个过程更加优雅。如果能够在编译时强制执行这样的多态性而不需要虚拟函数,那该多酷啊!而且还不需要五页难以阅读的模板工作。 - JoshK
@mistertribs 我同意,尤其是xskxzr的回答明确针对C++17。但是我认为这个问题不应该被标记为那个问题的重复。 - Brian Bi
我确实读过这个 - 我希望有一些更好的c++17/21解决方案,可以直接在元组中调用对象的方法。 - JoshK
很遗憾,如果你能使用基于范围的循环就好了,但实际上并不行。 - M.M
2个回答

10
有没有可能使用`std::apply`或者`std::invoke`来解决这个问题呢?
`std::apply`确实能够满足需求,通过折叠表达式实现:
std::tuple tplb{ Test{1} , Test{2} ,  Test{3} };

std::apply([](const auto&... tests){(tests.Go(), ...);}, tplb);

在这里,我们对tuple的每个类型值调用方法Go()
引用: 想法是能够在元组中拥有支持相同方法的其他对象类型。如果每个对象都存在该方法,则代码将可以编译和执行,而无需使用基类、虚函数、虚表等。
所以上述方法有效。 如果您希望根据类型分派到不同的实现,可以使用std::visit's example中的overloaded类。
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

auto f = overloaded {
        [](const Test& test) { test.Go(); },
        [](double d) { std::cout << d << ' '; },
        [](const std::string& s) { std::cout << s << ' '; },
    };
std::apply([&](const auto&... e){ (f(e), ...);}, my_tuple);

这让我可以使用一个完全不相关但实现了“Go()”方法的第二个类。太酷了,再次感谢。 - JoshK
using声明Ts::operator()...的目的是什么?由于Ts...是公开派生的,调整operator()成员函数的可访问性是否是不必要的? - 303
似乎应该在折叠表达式中调用 f 而不是 overloaded。目前,这会尝试从 my_tuple 包含的任何内容中派生,然后忽略构造的结果。 - 303
没有使用 Ts::operator()...;,调用将会产生歧义。 - Jarod42
确实,应该在std::apply中使用f,错别字已经修正。 - Jarod42
显示剩余2条评论

0
需要一些 std::index_sequence 技巧来访问元组的每个成员。
#include <iostream>
#include <tuple>
#include <utility>

class Test
{
    public:
        Test( int x ) : x_(x) {}; 
        void Go() const { std::cout << "Hi!" << x_ << "\n" ; } 
        int x_; 
};



template<typename F, typename T, std::size_t... Index>
void doStuffIndex(F&& action, T const& tup, std::index_sequence<Index...> const&)
{
    bool ignore[] = {((std::get<Index>(tup).*action)(), true)...};
}

template<typename F, typename... Obj>
void doStuff(F&& action, std::tuple<Obj...> const& tup)
{
    doStuffIndex(action, tup, std::make_index_sequence<sizeof...(Obj)>());
}

int main()
{
    std::tuple<Test, Test, Test> tplb{ Test{1} , Test{2} ,  Test{3} };
    doStuff(&Test::Go, tplb);
}

谢谢,但这不是我想问的。请看下面的答案,那个完全符合我的意思。请注意,在您的解决方案中仍然引用了特定的Class::Method()。这会限制在不同类上调用相同方法的能力。Jarod42下面的答案说得很好。 - JoshK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接