我已经开始使用这种类型的构造,它依赖于 C++20 的 lambda 显式模板参数:

template<typename... Ts>
struct Foo
{
  std::tuple<Ts...> bars;

  auto get_labels(const std::array<std::size_t,sizeof...(Ts)>& indices) const
  {
    // construct tuple from bar labels
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
      }(std::index_sequence_for<Ts...>{});
  }
};

在 C++17 或 C++14 中是否有相对优雅的方法可以做到这一点?或者我现在应该将 C++20 作为一项要求?

3

  • 使用特定语言标准本身并不是一项要求。但是,如果您可以使用此模板将代码移动到使用 C++20 编译的单独静态库,则可以将其余代码保留在 C++14/17 中。(由于 ABI 兼容性,只要您不在“接口”上公开 C++20 内容,不同版本之间的链接应该没问题)


    – 

  • @PepijnKramer 不幸的是,这是一个只有头文件的库。(这就是我所说的要求——让 C++20 成为库的消费者/用户的要求。)


    – 


  • GCC/Clang 即使在 C++14 中也支持模板 lambda,这意味着你可以直接


    – 



最佳答案
4

因此,认清您真正需要的是哪个部分非常重要。当您写下以下内容时:

// construct tuple from bar labels
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
    return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
}(std::index_sequence_for<Ts...>{});

您实际需要的是Is...。或者,更一般地说,您实际需要的是一组常量值。在上面的例子中,您接受一个参数index_sequence<Is...>– 这是一个参数,其类型包含一组常量值。

但另一种方法是接受N不同的参数,其中第一个参数是 类型integral_constant<size_t, 0>,第二个参数是 类型integral_constant<size_t, 1>,依此类推。如果你可以生成这些参数,那么 lambda 部分就变成了

[&](auto... Is){
    return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
}

请注意,主体是相同的,我只是改变了参数的样子。现在这是一个有效的 C++14 lambda。

因此,问题的其余部分是生成函数模板with<N>(f),该模板调用f(integral_constant<size_t, 0>{}, integral_constant<size_t, 1>{},..., integral_constant<size_t, N-1>{})允许您调用:

return with<sizeof...(Ts)>([&](auto... Is){
    return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
})

而且with用 C++14 编写很简单。这实际上只是同样的index_sequence技巧,但有一个额外的间接寻址(因为您需要将 one 更改index_sequenceN integral_constants)。可以说,结果看起来也更好 – 它不那么繁忙。无论如何,我更喜欢 C++20 中的这个。

3

  • 只是为了确保我没有遗漏任何东西;每个Is将是不同 Xauto...Is的一个实例。然后中的将被转换为整数,因为有一个转换运算符是 constexpr。是这样吗?std::integral_constant<size_t,X>Isget<Is>std::integral_constant


    – 

  • @edrezen 是的,完全正确。


    – 

  • 好的,谢谢。你确实说服了我使用类似方案with来迭代整数范围,例如在为 提供参数的情况下std::get


    – 

一种可能的方法是将序列直接“放置”在类模板中:

template <typename Seq, typename... Ts> struct FooImpl;

template <std::size_t... Is, typename... Ts>
struct FooImpl<std::index_sequence<Is...>, Ts...>
{
  static_assert(sizeof...(Is) == sizeof...(Ts));
  std::tuple<Ts...> bars;

  auto get_labels(const std::array<std::size_t, sizeof...(Ts)>& indices) const
  {
    // construct tuple from bar labels
    return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
  }
};

template <typename... Ts>
using Foo = FooImpl<std::make_index_sequence<sizeof...(Ts)>, Ts...>;

1

  • 1
    @Barry 的答案可能最适合我实际提出的问题,但我认为这个答案是我想要的。这使我避免在类中立即调用一堆 lambda。而且我已经在impl::命名空间中实现了,所以这甚至不会改变我的接口。


    – 

一种直接的方法是将 lambda 变成成员函数:

template<typename... Ts>
struct Foo
{
  std::tuple<Ts...> bars;

  template <std::size_t... Is>
  auto get_labels_(
    const std::array<std::size_t, sizeof...(Ts)>& indices,
    std::index_sequence<Is...>
  ) const
  {
    return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
  }

  auto get_labels(const std::array<std::size_t,sizeof...(Ts)>& indices) const
  {
    // construct tuple from bar labels
    return get_labels_(indices, std::index_sequence_for<Ts...>{});
  }
};

但不确定它是否算得上相对优雅。

1

  • 嗯,我想这可行。这实际上会使我的类的长度增加一倍,所以这不是很优雅;至少我可以将实现函数隐藏为私有函数,我想。


    – 

解决缺少模板 lambda 问题的一种方法是使用返回 lambda 的模板方法:

template<typename... Ts>
struct Foo
{
    std::tuple<Ts...> bars;

    template <std::size_t... Is> 
    auto make_lambda (std::index_sequence<Is...>, const std::array<std::size_t,sizeof...(Ts)>& indices) const
    {
        return [&] (std::index_sequence<Is...>) 
        {
            return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
        };
    }
 
    auto get_labels (const std::array<std::size_t,sizeof...(Ts)>& indices) const
    {
        auto seq = std::index_sequence_for<Ts...>{};
        return make_lambda (seq, indices) (seq);
    }
};

这应该从 c++14 开始起作用。

注意:这在某种程度上类似于我在发布之前没有见过的先前答案。也许它更强调了潜在的意图,即制作 lambda 模板,但更加不友好。

因此,如果您可以在自己的环境中使用 c++20,那么它是一个很好的举措。