c++:可变参数模板和函数重载

c++ : variadic template and function overloading

查看下面的实时示例:https://onlinegdb.com/Hkg6iQ3ZNI

#include <iostream>
#include <utility>
#include <type_traits>

class A 
{
    public:
    A(int v=-10):v_(v){}
    void print()
    {
        std::cout << "called A: " << v_ << std::endl;
    }
    private:
    int v_;
};

void f(int v)
{
    std::cout << "called f: " << v << std::endl;
    
}


template<typename T,typename ... Args>
void run(A&& a,
         T&& t,
         Args&& ... args)
{
    a.print();
    t(std::forward<Args>(args)...);
}


template<typename T,typename ... Args>
void run(T&& t,
          Args&& ... args)
{
  run(A(),
      std::forward<T>(t),
      std::forward<Args>(args)...);
}

int main()
{
    int v_function=1;
    int v_a = 2;
    
    run(f,v_function);
    
    return 0;
}

以上代码编译、运行并打印(如预期):

called A: -10

called f: 1

但是如果将main函数修改为:

int main()
{
    int v_function=1;
    int v_a = 2;
    
    run(f,v_function);
    
    // !! added lines !!

    A a(v_a);
    run(a,f,v_function);
    
    return 0;
}

然后编译失败并出现错误:

main.cpp:30:6: error: no match for call to ‘(A) (void (&)(int), int&)’

t(std::forward(args)...);

~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

这似乎表明即使将 A 的实例作为第一个参数传递,重载函数

void(*)(T&&,Args&&...) 

被调用,而不是

void(*)(A&&,T&&,Args&&...) 

template<typename T,typename ... Args>
void run(A&& a,
         T&& t,
         Args&& ... args)

a 不是转发引用,而是右值引用。这意味着当您执行 run(a,f,v_function); 时,该函数将不会被选中,因为 a 是一个左值并且不能绑定到右值引用。有两种快速方法可以解决此问题。首先,在 a 上使用 std::move 就像

run(std::move(a),f,v_function);

但这不是很好。 a 实际上并没有在函数中移动,所以你有点违反了最小惊喜原则。

第二个选项是将函数中的 A 设为模板类型,使其成为转发引用,然后您可以将其约束为 A 类型,例如

template<typename A_, typename T,typename ... Args, std::enable_if_t<std::is_same_v<std::decay_t<A_>, A>, bool> = true>
void run(A_&& a,
         T&& t,
         Args&& ... args)
{
    a.print();
    t(std::forward<Args>(args)...);
}

如果您使用 rvalue 调用 run,则您的代码有效。

Playable example here.

正如 NathanOliver 已经悲伤的那样:void run(A&& a, T&& t, Args&& ... args) 期待 rvalue reference.

rvalue reference 的基本思想:您正在将 rvalue 传递给函数(例如字符串文字)。该值将被复制到函数中。这项工作是不必要的。相反,您只是 "moving" 对该值的引用,因此它是由程序的不同部分 "owned" 。 Move constructors 是理解此问题的良好起点。