Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unpack arguments from variadic template

Tags:

c++

templates

For one of my project, I need the use of variadic templates. Everything works well, except the unpack of the arguments.

Here's the call

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

And the class that causes the problem

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());
            });
        }
};

And the error

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

I tried several things, like

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

But nothing works.

And here's the Shader class, just in case

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_;
};

Thanks!

like image 447
bl4ckb0ne Avatar asked Dec 30 '25 21:12

bl4ckb0ne


1 Answers

This is not really about variadic template unpacking. The problem is when declaring shaders, you need to tell its type. Is it a vector, an array, a tuple, etc.? Since you're using variadic template for that, im guessing shaders can have different types. You would then have to use a tuple.

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

Iterating on a tuple is not as trivial as an stl container though. Here's an example that use recursion.

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
}

Overall, it's quite intuitive, we start with i = 0, call f() then use recursion to traverse every i until N. C++14 allows you to avoid recursion with std::integer_sequence, which you could search on.

If your wondering about this && nonsense, I would suggest you to read about Universal References. In short, it allows you to prevent copying arguments using references, while making it possible to handle r-values. I would suggest you to do the same for Args... in Program constructor.

Then we can use tuple_for_each to do

tuple_for_each(shaders, [this](auto s) {
  std::cout << "glAttachShader\n";
  glAttachShader(program_, s.get_shader());
});
like image 156
etipdoray Avatar answered Jan 02 '26 13:01

etipdoray