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

时间:2017-09-04 08:46:35

标签: c++ c++11 templates bind variadic-templates

我有以下模板方法:

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兼容,并且有一个错误阻止在lambda中使用variadic args :(

更新

好的,所以添加std :: decay(如评论中所示)并没有完全解决问题,第一个参数不断推导到Window*&&而不是Window*,但实际指定了func的类型(例如std::function<SceneBasePtr (Window*)>),而不是使用auto编译的东西。

我不确定为什么会这样......

1 个答案:

答案 0 :(得分: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]);

请注意,引用折叠规则会将第一个参数转换为r值引用,而其余参数则为l值引用。

std::bind也会推断出类型。但是,std::bind将要存储参数,以便以后可以重用它们。问题是std::bind无法存储const char[5]。相反,它将衰减每个参数的类型。这意味着每个原始字符串文字将变为const char*,并将作为此类型的l值参数传递给绑定函数,这与手动实例化的create不匹配。类似的问题是window参数。它被推导为l值,但是,手动实例化的create函数需要r值。

最好不要明确指定函数模板的模板类型。如果由于某些原因(bug?)你无法在lambda中使用args...,你可以生成一个正确的create签名:

&T::template create<Window*&, typename std::decay<Args>::type&...>
//                        ~^~          ~~~~~~~~~^  

最终:

static SceneBasePtr create(Window*&, const char*&, const char*&);