参数包扣除不一致int和int&在可变参数模板成员函数中,它创建一个运行成员函数的线程

时间:2017-08-02 03:49:20

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

当我尝试运行此玩具示例时,我收到了一个不一致的参数包编译器错误。有人可以说明为什么'int a'被推断为int&这里?在下面的示例中,当我使用int literal运行下面的'test'函数时,它可以正常工作。提前感谢您的解释!

class Test {
    public:
    Test() {}

    ~Test() {
        t.join();
     }

    void print(int num)
    {
        std::cout << num << std::endl;
    }

    template<class ...Args>
    void test(void(Test::*b)(Args...) , Args&&... args)
    {
        t = std::thread(b, this, std::forward<Args>(args)...);
    }

    std::thread t;
    };

int main()
{
    int a = 123;
    Test test;
    test.test(&Test::print, a);
    // test.test(&Test::print, 123); works
}

错误:

prog.cc: In function 'int main()':
prog.cc:82:40: error: no matching function for call to 'Test::test( 
void (Test::*)(int), int&)'
     test.test(&Test::print, a);
                               ^     
prog.cc:82:40: note: candidate is:
prog.cc:62:10: note: template<class ... Args> void Test::test(void 
(Test::*)(Args ...), Args&& ...)
     void test(void(Test::*b)(Args...) , Args&&... args)
      ^
prog.cc:62:10: note:   template argument deduction/substitution failed:
prog.cc:82:40: note:   inconsistent parameter pack deduction with 'int' and 
'int&'
     test.test(&Test::print, a);
                               ^     

2 个答案:

答案 0 :(得分:6)

绝不要使用推断的前向引用类型与其他参数完全匹配。

当您将左值Args推断int传递为Args时,完美转发int&。然后int& &&折叠为int&

简而言之,绝不使用推断的前向引用类型与其他参数完全匹配

有一些罕见的例外,但这是在库式代码中,其中一个类型已经在早期的上下文中从另一个类推断出来。

此:

template<class ...Args>
void test(void(Test::*b)(Args...) , Args&&... args)
{
    t = std::thread(b, this, std::forward<Args>(args)...);
}

受到限制。

尝试:

template<class F, class ...Args>
void test(F&& f, Args&&... args)
{
    t = std::thread(std::forward<F>(f), this, std::forward<Args>(args)...);
}

第一个arg到底是什么不是你的问题。它可以是指向this的成员函数指针,它可以是一个可以将this作为其第一个参数的对象。

如果出于某种原因你想要坚持第一个参数是成员函数指针:

template<class R, class...A0s, class ...Args>
void test(R(Test::*f)(A0s...), Args&&... args)
{
    t = std::thread(f, this, std::forward<Args>(args)...);
}

不要过度约束它。如果你真的想确保错误发生在test的调用而非其身体内,我们可以这样做:

template<class R, class...A0s, class ...Args>
auto test(R(Test::*f)(A0s...), Args&&... args)
-> decltype((void)((std::declval<Test*>()->*f)(std::declval<typename std::decay<Args>::type>()...) )>
{
    t = std::thread(f, this, std::forward<Args>(args)...);
}

我们SFINAE根据能够使用this->*f的朽烂副本调用args...来禁用此功能。

这通常是矫枉过正的。

最后,我们可以这样做:

template<class T> struct tag_t{using type=T;};
template<class T> using no_deduction=typename tag_t<T>::type;

template<class ...Args>
void test(void(Test::*b)(Args...) , no_deduction<Args>... args)
{
    t = std::thread(b, this, std::forward<Args>(args)...);
}

我们阻止对参数的推论,并且只对函数指针进行模式匹配。如果b想要参考,这不起作用;我们需要一些额外的类型编程来将T&变换为std::reference_wrapper<T>

template<class T>
struct compatible_arg { using type=T; };
template<class T>
struct compatible_arg<T&> {
  using type=std::reference_wrapper<T>;
};
template<class T>
using compatible_arg_t = typename compatible_arg<T>::type;

template<class ...Args>
void test(void(Test::*b)(Args...) , compatible_arg_t<Args>... args)
{
    t = std::thread(b, this, std::forward<decltype(args)>(args)...);
}

T&&映射到T&&,将T映射到T,将T&映射到std::reference_wrapper<T>

但实际上,请停留在:

template<class F, class ...Args>
void test(F&& f, Args&&... args)
{
    t = std::thread(std::forward<F>(f), this, std::forward<Args>(args)...);
}

答案 1 :(得分:4)

template<class ...Args>
void test(void(Test::*b)(Args...) , Args&&... args)
{
    t = std::thread(b, this, std::forward<Args>(args)...);
}

当你做这样的事情时,它意味着:

  • 从作为第一个参数传递的PMF中推断出Args...
  • 然后独立于其余参数的类型和值类别推断Args...
  • 两次独立扣除的结果必须匹配,否则是错误。

这实际上永远不会你真正想做的事情。函数参数的类型与其相应参数的类型和值类别之间通常没有精确匹配关系。

在这里,您实际上并不需要PMF的参数类型(更不用说您必须编写大量的重载以涵盖cv-和ref-qualifiers的所有可能组合),因此您可以将其限制为“指针”某某类型的Test成员“:

template<class F, class... Args>
void test(F Test::* b, Args&&... args)
{
    t = std::thread(b, this, std::forward<Args>(args)...);
}

或者只是让它不受约束:

template<class F, class... Args>
void test(F&& f, Args&&... args)
{
    t = std::thread(std::forward<F>(f), this, std::forward<Args>(args)...);
}

或者,您可以引入一个新包:

template<class ...Args, class... Args2>
void test(void(Test::*b)(Args...) , Args2&&... args)
{
    t = std::thread(b, this, std::forward<Args2>(args)...);
}