传递可变参数到模板函数时出现编译错误

3

我有以下模板方法:

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

基本上我需要做的就是将可变参数Args绑定到T::create(它本身是一个静态的可变参数模板方法),但允许在调用时单独填充第一个参数(窗口)。
以上代码会出现以下错误。
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);
                    ~~~~^~~~~~~~

当以以下方式调用代码时:
manager.register_scene<SceneWithArgs>("test", "arg");

我并不真正理解这个错误,也不知道如何修复它。

起初我通过在lambda内部简单调用create来解决此问题,在GCC 4.9及以上版本上运行良好,但我必须保持与GCC 4.8.4的兼容性,并且存在一个bug,防止在lambda内使用可变参数:(

更新

好吧,添加std :: decay(如评论中所示)并没有完全解决问题,第一个参数仍然会推断为 Window *&&而不是 Window *,但实际上指定 func 类型(例如 std :: function< SceneBasePtr (Window *)>)而不是使用auto使事情编译。

但我不确定为什么...


你可能想把 create<Window*, Args&...> 改成 create<Window*, typename std::decay<Args>::type&...> - StoryTeller - Unslander Monica
1个回答

2
您没有展示create的声明,但我假设它如下所示:
template <typename... Args>
static SceneBasePtr create(Args&&...);

看起来不错。你使用了转发引用,这样编译器就可以推断出参数的确切类型。只要编译器推断出类型,这种方法就能够奏效。但是...

 &T::template create<Window*, Args&...>

在这里,您明确实例化函数模板。也就是说,编译器将不再推断类型,而是将使用您提供的模板参数来替换模板参数。那么这些模板参数是什么?

template <typename T, typename... Args>
void register_scene(const std::string& name, Args&&... args);
//...
manager.register_scene<SceneWithArgs>("test", "arg");

第一个显然的参数是Window*,它是明确传递的。现在看Args...——"test""arg"是原始字符串字面量,它们对应的类型分别是const char[5]const char[4]。然后,通过推断过程,Args...变成了const char(&)[5]const char(&)[4]。那么,当你用这些类型实例化create时会发生什么呢?你将得到以下声明:
static SceneBasePtr create(Window*&&, const char(&)[5], const char(&)[4]);

请注意,引用折叠规则将第一个参数转换为右值引用,而其余参数则为左值引用。 std::bind 也会推断类型。然而,std::bind 希望存储参数,以便稍后重复使用。问题在于,std::bind 无法存储 const char[5]。相反,它将衰减每个参数的类型。这意味着每个原始字符串字面量将变成 const char*,并作为此类型的左值参数传递给绑定函数,这与手动实例化的 create 不匹配。类似的问题还出现在 window 参数上。它被推断为左值,然而,手动实例化的 create 函数期望一个右值。
最好不要显式指定函数模板的模板类型。如果由于某些原因(错误?)您无法在 lambda 内部使用 args...,您可以生成一个正确的 create 签名。
&T::template create<Window*&, typename std::decay<Args>::type&...>
//                        ~^~          ~~~~~~~~~^  

为了达到最终目标:
static SceneBasePtr create(Window*&, const char*&, const char*&);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接