简体   繁体   中英

c++ : variadic template and function overloading

see example below live : 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;
}

The code above compiles, runs and print (as expected):

called A: -10

called f: 1

but if the main function is modified to:

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

then compilation fails with error:

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

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

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

which seems to indicate that even when an instance of A is passed as first argument, the overload function

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

is called, and not

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

With

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

a is not a forwarding reference, but an rvalue reference. That means when you do run(a,f,v_function); that function will not be selected because a is an lvalue and those can't be bound to rvalue references. There are two quick ways to fix this. First, use std::move on a like

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

but this isn't great. a isn't actually moved in the function so you are kind of violating the principle of least surprise.

The second option is to make A in the function a template type so it becomes a forwarding reference and then you can constrain it to be of type A like

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

Your code works, if you are calling run with an rvalue .

Playable example here.

As NathanOliver already sad: void run(A&& a, T&& t, Args&& ... args) expects an rvalue reference .

Basic idea of an rvalue reference : You are passing an rvalue to a function (eg a string literal). That value will be copied to the function. This work is unnecessary. Instead, you are just "moving" the reference to that value, so that it is "owned" by a different part of your program. Move constructors are a good starting point for understanding this problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM