Compilation error when passing variadic args to a template function
I have the following template method:
template<typename T, typename... Args>
void register_scene(const std::string& name, Args&&... args) {
auto func = std::bind(&T::template create<Window*, Args&...>, std::placeholders::_1, std::forward<Args>(args)...);
_store_scene_factory(name, [=](Window* window) -> SceneBasePtr {
auto ret = func(window);
ret->set_name(name);
return ret;
});
}
Essentially all I need to do is bind the variadic Args
to T::create
(which itself is a static variadic template method), but allow populating the first argument (window) separately when it's called.
The above code fails with the following error
error: no match for call to ‘(const std::_Bind<std::shared_ptr<{anonymous}::SceneWithArgs> (*(std::_Placeholder<1>, const char*))(smlt::Window*&, const char (&)[4])>) (smlt::Window*&)’
auto ret = func(window);
~~~~^~~~~~~~
When calling the code like this:
manager.register_scene<SceneWithArgs>("test", "arg");
I don't really understand the error, or how to fix it.
I initially solved this by simply calling create inside the lambda, and this works on GCC 4.9 and above, but I have to remain compatible with GCC 4.8.4 and there's a bug which prevents using variadic args inside a lambda :(
UPDATE
OK so adding std::decay (as seen in the comments) didn't fix the problem entirely, the first argument kept deducing to Window*&&
rather than Window*
, but actually specifying the type of func
(eg std::function<SceneBasePtr (Window*)>
) instead of using auto
made things compile.
I'm not sure why that is though...
You did not show the declaration of create
, but I assume it looks as follows:
template <typename... Args>
static SceneBasePtr create(Args&&...);
It looks good. You use forwarding references, so that the compiler can deduce the exact types of arguments. As long as the compiler deduces the types, this will work. However...
&T::template create<Window*, Args&...>
Here, you instantiate the function template explicitly. That is, the compiler will no longer deduce the types, instead, it will replace the template parameters by the template arguments you provide. What are these template arguments?
template <typename T, typename... Args>
void register_scene(const std::string& name, Args&&... args);
//...
manager.register_scene<SceneWithArgs>("test", "arg");
The first one is passed explicitly, it's Window*
, this one is obvious. Now Args...
-- "test"
and "arg"
are raw string literals, and their corresponding types are const char[5]
and const char[4]
, respectively. Then Args...
(after a deduction process) become const char(&)[5]
and const char(&)[4]
. Then what happens now, when you instantiate create
with these types? You will end up with a declaration as below:
static SceneBasePtr create(Window*&&, const char(&)[5], const char(&)[4]);
Note that reference collapsing rules turn the first parameter into an r-value reference, while the rest are l-value references.
std::bind
deduces types as well. However, std::bind
will want to store the arguments, so that they can be reused later. The problem is that std::bind
cannot store const char[5]
. Instead, it will decay the type of each argument. This means that each raw string literal will become const char*
, and will be passed as an l-value argument of this type to the bound function, and this does not match the manually instantiated create
. A similar problem is with the window
argument. It is deduced as an l-value, however, the manually instantiated create
function expects an r-value.
It's best not to explicitly specify template types of a function template. If for some reasons (bug?) you cannot use args...
inside the lambda, you can generate a proper create
signature:
&T::template create<Window*&, typename std::decay<Args>::type&...>
// ~^~ ~~~~~~~~~^
To end up with:
static SceneBasePtr create(Window*&, const char*&, const char*&);
链接地址: http://www.djcxy.com/p/51586.html
上一篇: 可变模板函数的包装模板
下一篇: 将可变参数传递给模板函数时出现编译错误