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

上一篇: 可变模板函数的包装模板

下一篇: 将可变参数传递给模板函数时出现编译错误