【问题标题】:C++14: How to group variadic inputs by template parameter?C++14:如何通过模板参数对可变参数输入进行分组?
【发布时间】:2018-11-08 19:42:45
【问题描述】:

假设我有两个班级:

template <unsigned N>
class Pixel {
    float color[N];
public:
    Pixel(const std::initializer_list<float> &il)
    {
      // Assume this code can create a Pixel object from exactly N floats, and would throw a compiler error otherwise

    }
};

template <unsigned N>
class PixelContainer {
    std::vector<Pixel<N>> container;
};

我要做的是为PixelContainer 编写一个构造函数,这样: 它会在以下情况下正确实例化(例如,并非详尽无遗):

PixelContainer<3> pc1(1, 2, 3)          // Creates a container containing one Pixel<3> objects
PixelContainer<3> pc2(1, 2, 3, 4, 5, 6) // Creates a container containing 2 Pixel<3> objects
PixelContainer<2> pc3(1, 2, 3, 4, 5, 6) // Creates a container containing 3 Pixel<2> objects

以下情况无法编译(例如,并非详尽无遗):

PixelContainer<3> pc4(2, 3) // Not enough arguments
PixelContainer<2> pc5(1, 2, 3, 4, 5) // Too many arguments

如何使用模板元编程实现上述目标?我觉得它应该是可以实现的,但不知道如何实现。具体来说,我确实想自己进行分组,例如

PixelContainer<2> pc2({1, 2}, {3, 4}, {5, 6}) // Creates a container containing 3 Pixel<2> objects

(请参阅question 了解我背后的灵感)

【问题讨论】:

  • 在上一个 sn-p 中,您的意思肯定是 PixelContainer&lt;2&gt; 和“包含 3 个像素 对象”吗?
  • 没错,已更正!谢谢:)

标签: c++ templates initialization c++14 template-meta-programming


【解决方案1】:
template<class T, std::size_t I, std::size_t...Offs, class Tup>
T create( std::index_sequence<Offs...>, Tup&& tup ) {
  return T( std::get<I+Offs>(std::forward<Tup>(tup))... );
}

template <unsigned N>
struct Pixel {
    float color[N];

    template<class...Ts,
        std::enable_if_t< sizeof...(Ts)==N, bool > = true
    >
    Pixel(Ts&&...ts):color{ std::forward<Ts>(ts)... } {};
};

template <unsigned N>
struct PixelContainer {

    std::vector<Pixel<N>> container;
    template<class T0, class...Ts,
      std::enable_if_t<!std::is_same<std::decay_t<T0>, PixelContainer>{}, bool> =true
    >
    PixelContainer(T0&& t0, Ts&&...ts):
      PixelContainer( std::make_index_sequence<(1+sizeof...(Ts))/N>{}, std::forward_as_tuple( std::forward<T0>(t0), std::forward<Ts>(ts)... ) )
    {}
    PixelContainer() = default;
private:
  template<class...Ts, std::size_t...Is>
  PixelContainer( std::index_sequence<Is...>, std::tuple<Ts&&...>&& ts ):
    container{ create<Pixel<N>, Is*N>( std::make_index_sequence<N>{}, std::move(ts) )... }
  {}
};

create 采用类型、起始索引和偏移索引序列。然后它需要一个元组。

然后它从(起始索引)+(每个偏移量)创建类型并返回它。

我们在 PixelContainer 的私有 ctor 中使用它。对于每个像素在tuple 中的元素,它都有一个索引序列元素。

我们将索引序列元素乘以 N,即每个索引序列的元素数,然后将其传递给创建。此外,我们为偏移量和主元组传入一个 0,...,N-1 的索引序列。

然后我们将其解压缩到 {} 封闭的 ctor 中,用于 container

公共 ctor 只是转发到具有每个元素一个的正确索引包的私有 ctor(等于参数计数/N)。它有一些 SFINAE 烦人 enable_if_t 的东西,以避免它吞下应该去复制 ctor 的东西。

Live example.

还有,

  std::enable_if_t<0 == ((sizeof...(Ts)+1)%N), bool> =true

可能是对 PixelContainer 的公共 ctor 的一个有用的 SFINAE 补充。

没有它,我们只需向下舍入并丢弃传递给PixelContainer 的“额外”元素。有了它,如果我们有额外的元素(即不是 N 的倍数),我们会得到“找不到 ctor”。

【讨论】:

  • 我认为 OP 会感谢它为什么以及如何工作的一些启发。
  • create的偏移包名不一致,应该是Is
  • @SombreroChicken 如果一个人寻求启蒙,一个人应该寻求成为佛陀。如果一个人寻求混淆,应该学习 C++。
  • @Pezo 已修复!成功了Offs,因为它们是偏移量。
  • @Yakk-AdamNevraumont - 答案在一篇文章中教会了我很多 std:: 构造!您能否详细说明最后一次编辑,关于 SFINAE 对公共参与者的补充(以及它可能有用的地方)?感谢您完善答案和现场示例!
【解决方案2】:

也做了一些事情,这比@Yakk 的回答更依赖于编译器的性能优化。

它使用临时的std::arrays。 temp 用于将传递的值存储在某处。 temp_pixels 用于从temp 复制像素数据。最后将temp复制到container

我相信那些数组确实会被优化掉,但我不确定。看看godbolt,它们似乎是,但我不擅长阅读编译器汇编输出:)

#include <array>
#include <algorithm>
#include <cstddef>
#include <vector>

template <unsigned N>
struct Pixel {
    float color[N]; // consider std::array here
};

template <unsigned N>
class PixelContainer {
    std::vector<Pixel<N>> container;

public:
    template<class... Ts>
    PixelContainer(Ts... values)
    {
        static_assert(sizeof...(Ts) % N == 0, "Pixels should be grouped by 3 values in PixelContainer constructor");
        const std::array<float, sizeof...(Ts)> temp{float(values)...};
        std::array<Pixel<N>, sizeof...(Ts) / N> temp_pixels{};

        for (std::size_t i = 0; i < sizeof...(Ts); i += N)
        {
            auto& pixel = temp_pixels[i / N];

            std::copy(
                temp.begin() + i, temp.begin() + i + N,
                pixel.color
            );
        }

        container = std::vector<Pixel<N>>(temp_pixels.begin(), temp_pixels.end());
    }
};

int main()
{
    PixelContainer<3> pc1(1, 2, 3);          // Creates a container containing one Pixel<3> objects
    PixelContainer<3> pc2(1, 2, 3, 4, 5, 6); // Creates a container containing 2 Pixel<3> objects
    PixelContainer<2> pc3(1, 2, 3, 4, 5, 6); // Creates a container containing 3 Pixel<2> objects

/*
    PixelContainer<3> pc4(2, 3); // Not enough arguments
    PixelContainer<2> pc5(1, 2, 3, 4, 5); // Too many arguments
*/
}

【讨论】:

  • 不错的替代方法,更易于阅读。感谢您提供另一种观点!
【解决方案3】:

我会建议一些混合版本,仍然有一个临时数组,但没有临时像素

template <unsigned N>
struct PixelContainer {

    template<std::size_t S, std::size_t... elts>
    auto createOne(const std::array<float, S> &arr, std::size_t offset,
                   std::index_sequence<elts...>) {
        return Pixel<N>{ arr[offset + elts]... };
    }

    template<typename... Floats>
    PixelContainer(Floats... vals) {
        static_assert(sizeof...(vals) % N == 0, "invalid number of args");
        std::array<float, sizeof...(vals)> arr = { float(vals)... };
        for (size_t i = 0; i < sizeof...(vals) / N; i++) {
            container.push_back(createOne(arr, i * N, std::make_index_sequence<N>{}));
        }

    }
    std::vector<Pixel<N>> container;
};

【讨论】:

    【解决方案4】:

    我认为您提供的链接(C++ number of function's parameters fixed by template parameter)中已经给出了答案。你只需要在那里更改断言:而不是sizeof...(Floats) == N,你需要sizeof...(Floats) % N == 0

    【讨论】:

    • 这并没有告诉我如何对输入进行分组,以便我可以一个一个实例化 Pixel 对象。让我尝试编辑问题以反映这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-01
    • 2018-02-20
    • 2015-08-18
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多