Bl4ckb0ne Bl4ckb0ne - 2 months ago 15
C++ Question

Unpack arguments from variadic template

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!

Answer

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