Для одного из моих проектов мне нужно использовать вариативные шаблоны. Все работает хорошо, кроме распаковки аргументов.

Вот звонок

Shader<GL_VERTEX_SHADER> vert(vertexShaderSource);
Shader<GL_FRAGMENT_SHADER> frag(fragmentShaderSource);
Program prog(vert, frag);

И класс, вызывающий проблему

class Program
{
    public:
        template <class... Args>
        Program(Args... args) :
            program_(glCreateProgram())
        {         
            auto shaders {{args...}};

            std::for_each(shaders.begin(), shaders.end(), [this](auto s)       
            {
                std::cout << "glAttachShader\n";
                glAttachShader(program_, s.get_shader());
            });
        }
};

И ошибка

fatal error: cannot deduce type for variable 'shaders' with type 'auto' from nested initializer list
    auto shaders {{args...}};

Я пробовал несколько вещей, например

auto shaders = {args...};
auto shaders = {{args...}};
auto shaders {args...};    

Но ничего не работает.

А вот и класс Shader на всякий случай

template <GLenum type>
class Shader
{
    public:
        Shader(std::string const &source)
        {
            char const *src = source.c_str();
            shader_ = glCreateShader(type);
            glShaderSource(shader_, 1, &src, NULL);
            glCompileShader(shader_);
        }

        ~Shader()
        {
            glDeleteShader(shader_);
        }

        inline GLuint get_shader()
        {
            return shader_;
        }

    private:
        GLuint shader_;
};

Благодарность!

3
bl4ckb0ne 19 Сен 2016 в 22:48

4 ответа

Лучший ответ

На самом деле речь идет не о распаковке вариативного шаблона. Проблема в том, что при объявлении шейдера нужно указать его тип. Это вектор, массив, кортеж и т. Д.? Поскольку для этого вы используете вариативный шаблон, я предполагаю, что шейдеры могут быть разных типов. Тогда вам придется использовать кортеж.

auto shaders = std::make_tuple(args...);

Однако итерация по кортежу не так проста, как с контейнером stl. Вот пример использования рекурсии.

template <size_t i = 0,
          class Fun,
          class Tuple,
          size_t N = std::tuple_size<typename std::decay<Tuple>::type>::value,
          std::enable_if_t<i >= N>* = nullptr> // if i >= N
void tuple_for_each(Tuple&& t, Fun f) {} // end case

template <size_t i = 0,
          class Fun,
          class Tuple,
          size_t N = std::tuple_size<typename std::decay<Tuple>::type>::value,
          std::enable_if_t<i < N>* = nullptr> // if i < N
void tuple_for_each(Tuple&& t, Fun f) {
  f(std::get<i>(std::forward<Tuple>(t))); // current iteration
  tuple_for_each<i+1>(std::forward<Tuple>(t), std::move(f)); // call next
}

В целом, это довольно интуитивно понятно, мы начинаем с i = 0, вызываем f (), затем используем рекурсию для обхода каждого i до N. C ++ 14 позволяет избежать рекурсии с помощью std :: integer_sequence, по которому вы можете искать.

Если вас интересует эта && ерунда, я предлагаю вам прочитать о Универсальные справочные материалы. Короче говоря, он позволяет предотвратить копирование аргументов с помощью ссылок, позволяя при этом обрабатывать r-значения. Я предлагаю вам сделать то же самое для Args ... в конструкторе программ.

Затем мы можем использовать tuple_for_each для выполнения

tuple_for_each(shaders, [this](auto s) {
  std::cout << "glAttachShader\n";
  glAttachShader(program_, s.get_shader());
});
6
etipdoray 19 Сен 2016 в 20:41

В этом случае хорошо подходит диапазон, основанный на цикле for.
Кроме того, постарайтесь избегать дополнительных копий, используя auto непосредственно в лямбде.
Вот минимальный рабочий пример:

#include<iostream>

class Program {
public:
    template <class... Args>
    Program(Args... args) {
        for(auto &&arg: { args... }) {
            std::cout << "do whatever you want" << std::endl;
        }
    }
};

int main() {
    Program p{42, 0, 1};
}

Вот ваш проверенный код:

class Program {
public:
    template <class... Args>
    Program(Args... args) : program_(glCreateProgram()) {
        for(auto &&s: { args... }) {
            std::cout << "glAttachShader" << std::endl;
            glAttachShader(program_, s.get_shader());
        }
    }
};
0
skypjack 20 Сен 2016 в 05:47

Другой способ - поместить сложный материал в отдельный частный метод:

class Program
{
    template<class ShaderTuple, std::size_t...Is>
    void attach_shaders(const ShaderTuple& shaders, std::index_sequence<Is...>)
    {
        using expand = int[];
        void(expand
        {
            0,
            (
             std::cout << "glAttachShader\n",
             glAttachShader(program_, std::get<Is>(shaders).get_shader()),
             0
             )...
        });

    }

public:
    template <class... Shaders>
    Program(Shaders&&... args)
    : program_(glCreateProgram())
    {
        attach_shaders(std::make_tuple(std::forward<Shaders>(args)...),
                       std::make_index_sequence<sizeof...(Shaders)>());
    }
};
2
Richard Hodges 19 Сен 2016 в 20:26

Вместо использования std::for_each вы можете использовать массив для распаковки ваших аргументов.

Хитрость заключается в использовании инициализации списка для распаковки аргументов, вызова лямбда-выражения и использования оператора , для удаления значения и использования вместо него 0.

struct Program {
    template <class... Args>
    Program(Args... args) : program_(glCreateProgram()) {  
        int unpack[] = {([this](auto& shader){
            std::cout << "glAttachShader\n";
            glAttachShader(program_, s.get_shader());
        }(args), 0)..., 0};

        // use this to silent the warning
        static_cast<void>(unpack);
    }
};

Вы можете обобщить этот «pack foreach», отправив лямбда вместо жестко запрограммированного:

template<typename F, typename... Args>
void pack_foreach(F f, Args&&... args) {
    int unpack[] = {(f(std::forward<Args>(args)), 0)..., 0};

    static_cast<void>(unpack);
}

Оставив свой код вот так:

struct Program {
    template <class... Args>
    Program(Args... args) : program_(glCreateProgram()) {  
        pack_foreach([this](auto shader){
            std::cout << "glAttachShader\n";
            glAttachShader(program_, s.get_shader());
        }, args...);
    }
};
4
Guillaume Racicot 19 Сен 2016 в 20:37