【问题标题】:c++ : variadic template and function overloadingc++:可变参数模板和函数重载
【发布时间】:2020-02-24 20:29:17
【问题描述】:

看下面的例子: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;
}

上面的代码编译、运行和打印(如预期的那样):

叫A:-10

称为 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&&...) 

【问题讨论】:

    标签: c++ variadic-functions overload-resolution


    【解决方案1】:

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

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

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

    【讨论】:

    • 当然值得另一个问题,或者在我这边进一步阅读,但是“std::decay_t”是什么?
    • @Vince 是删除类型的引用、const 和 volatile 限定的快捷方式。当您删除所有这些排列时,它可以更容易地检查其类型。
    • 虽然建议的解决方案在 onlinegdb.com 上运行良好,但在我的笔记本电脑上它没有编译,说重载调用不明确(仅传递要运行的函数时),好像启用是不工作。 gcc 版本为 7.4.0。
    • @Vince 你能分享一个minimal reproducible example的链接吗?
    • 哦,问题的代码是试图编写一个最小的可重复实验——;我的原始代码要复杂得多。
    【解决方案2】:

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

    Playable example here.

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

    rvalue reference 的基本概念:您将 rvalue 传递给函数(例如字符串文字)。该值将被复制到函数中。这项工作是不必要的。相反,您只是“移动”对该值的引用,以便它由程序的不同部分“拥有”。 Move constructors 是理解这个问题的一个很好的起点。

    【讨论】:

    • 虽然我确实明白了这个想法以及为什么代码在传递 r 值时会起作用,但我不明白的部分是如何创建一个运行函数,该函数将无差别地接受 r 和 l 值跨度>
    • 嗯。好问题...嗯,最简单的答案是通过副本,但我想这不是你想要的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-23
    • 2015-05-08
    • 1970-01-01
    • 2019-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多