C ++ 14: Как сгруппировать переменные входные данные по параметру шаблона? - PullRequest
0 голосов
/ 08 ноября 2018

Скажем, у меня есть два класса:

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

(см. Этот вопрос для вдохновения позади меня)

Ответы [ 4 ]

0 голосов
/ 08 ноября 2018

Я бы предложил гибридную версию, временный массив все еще существует, но для пикселей нет временных

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;
};
0 голосов
/ 08 ноября 2018
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 принимает тип, начальный индекс и последовательность индексов смещений. Затем требуется кортеж.

Затем он создает тип из (начальный индекс) + (каждое из смещений) и возвращает его.

Мы используем это в частном ctor PixelContainer. Он имеет элемент последовательности индекса для каждого из пикселей, элементы которого находятся в tuple.

Мы умножаем элемент индексной последовательности на N, количество элементов в индексной последовательности и передаем его для создания. Кроме того, мы передаем последовательность индексов 0, ..., N-1 для смещений и основной кортеж.

Затем мы распаковываем это в {} вложенный ctor для container.

Публичный ctor просто пересылает в приватный ctor с нужным пакетом индексов по одному на элемент (равным числу аргументов / N). В нем есть что-то, раздражающее SFINAE enable_if_t, чтобы он не проглатывал вещи, которые должны идти в копию.

Живой пример .

Кроме того,

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

может быть полезным дополнением SFINAE для публичного ctor PixelContainer.

Без этого мы просто округляем и отбрасываем «лишние» элементы, переданные в PixelContainer. С его помощью мы получаем «не найден ctor», если у нас есть дополнительные элементы (т.е. не кратные N).

0 голосов
/ 08 ноября 2018

Также сделал что-то, что больше зависит от оптимизации компилятора для производительности, чем от ответа @ Yakk.

Используются временные std::array с. temp используется для хранения переданных значений где-либо. temp_pixels используется для копирования данных пикселей из temp. Наконец temp копируется в container.

Я считаю, что эти массивы do оптимизируются, но я не уверен. Глядя на 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
*/
}
0 голосов
/ 08 ноября 2018

Я думаю, что ответ в значительной степени приведен в указанной вами ссылке ( C ++ число параметров функции, фиксированное параметром шаблона ). Вам просто нужно изменить там утверждение: вместо sizeof...(Floats) == N вам понадобится sizeof...(Floats) % N == 0.

...