参数包扣除不一致int和int&in variadic模板化成员函数,它创建一个运行成员函数的线程

Moz*_*zbi 2 c++ templates variadic-templates c++11

当我尝试运行这个玩具示例时,我得到一个不一致的参数包编译器错误.有人可以说明为什么'int a'被推断为int&here?在下面的示例中,当我使用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
}
Run Code Online (Sandbox Code Playgroud)

错误:

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);
                               ^     
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 6

切勿使用推导的前向引用类型与其他参数完全匹配.

完美转发Args当你传递一个左值int推断Argsint&.然后int& &&坍塌成int&.

简而言之,永远不要使用推导的前向引用类型来完全匹配其他参数.

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

这个:

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

受到限制.

尝试:

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

第一个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)...);
}
Run Code Online (Sandbox Code Playgroud)

不要过度约束它.如果你真的想确保错误发生在调用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)...);
}
Run Code Online (Sandbox Code Playgroud)

我们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)...);
}
Run Code Online (Sandbox Code Playgroud)

我们阻止对参数的推论,并且只对函数指针进行模式匹配.如果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)...);
}
Run Code Online (Sandbox Code Playgroud)

映射T&&T&&,TTT&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)...);
}
Run Code Online (Sandbox Code Playgroud)


T.C*_*.C. 5

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

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

  • Args...从作为第一个参数传递的 PMF 中进行推导。
  • 然后Args...独立于剩余参数的类型和值类别进行推导。
  • 两次独立推演的结果必须一致,否则就是错误。

这实际上从来都不是您真正想要做的。函数参数的类型与其相应参数的类型和值类别之间通常不存在精确匹配关系。

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

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

或者简单地让它不受约束:

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

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

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