【问题标题】:Deduced conflicting types in template pack with reference将模板包中的冲突类型推导出参考
【发布时间】:2019-08-20 05:36:03
【问题描述】:

我正在开发一个具有以下结构的程序:

#include <iostream>
#include <string>

void fun(const std::string &text, int a, int b) { // (1)
    std::cout << text << a + b << std::endl;
}

template<typename ...Args>
void execute(void(*fun)(Args...), Args ...args) {
    fun(args...);
}

void init(const std::string &text, int a, int b) {
    execute(fun, text, a, b);
}

int main() {
    init("Fun: ", 1, 2);
    return 0;
}

我收到错误消息

.code.tio.cpp:14:2: error: no matching function for call to 'execute'
        execute(fun, text, a, b);
        ^~~~~~~
.code.tio.cpp:9:6: note: candidate template ignored: deduced conflicting types for parameter 'Args' (<const std::__cxx11::basic_string<char> &, int, int> vs. <std::__cxx11::basic_string<char>, int, int>)
void execute(void(*fun)(Args...), Args ...args) {
     ^
1 error generated.

我可以通过删除第 (1) 行中的引用来修复错误:

void fun(const std::string text, int a, int b) {

但我想通过引用而不是值传递值。函数模板

template<typename ...Args>
void execute(void(*fun)(Args...), Args ...args)

不得更改。我该如何解决这个问题,以便 text 通过引用传递,execute 不会更改,init 也不会更改(如果可能)?

编辑: @super 表明我错了,我必须重新制定我的要求。 execute 只能在其他依赖此函数的项目不中断的情况下进行修改。我没有想过这样的解决方案。

【问题讨论】:

  • 您的代码不起作用,但您不想更改它?在这种情况下,我不明白你在寻找什么。
  • @super 我无法更改execute,因为它是许多不同项目中使用的框架的通用函数。不可能改变它。 init 可以更改,但会很昂贵。 fun 可以更改,因为它是必须与通用框架一起使用的新功能

标签: c++ c++11 templates variadic-templates template-argument-deduction


【解决方案1】:

建议:使用两组模板可变参数

template <typename ... As1, typename ... As2>
void execute(void(*fun)(As1...), As2 ... args) {
    fun(args...);
}

这样您可以在fun() 函数参数中维护引用并将字符串值传递给它。

更笼统地说:这是一场噩梦,推导出的函数参数集与以下参数的集完全相同。而且没必要。

假设你有函数foo() 接收long

void foo (long)
 { }

然后你调用execute() 传递一个foo() 指针和一个int

execute(foo, 1);

如果您使用单个 Args... 可变参数序列,则调用将在您的问题中失败,因为编译器将 Args... 推断为 long(来自 foo() 签名)和 long(来自值 1 ),所以含糊不清。

如果您使用两个可变参数序列,编译器将 long 推导出为 As1...,将 int 推导为 As2...,没有歧义,execute()int 值传递给需要long 值,这是完全合法的。

【讨论】:

    【解决方案2】:

    不碰execute,我认为你必须改变init()。一种方法是显式传递模板参数(绕过参数推导以传输引用类型信息):

    void init(const std::string &text, int a, int b) {
        execute<const std::string&>(fun, text, a, b);
    }
    

    【讨论】:

    • 试图编译它会得到Error C2782 'void execute(void (__cdecl *)(Args...),Args...)': template parameter 'Args' is ambiguous`
    • 我只看到一个“hello world”文件
    【解决方案3】:

    我不确定您为什么不想更改 execute,但在我看来,将其修改为对可调用对象使用单独的模板参数将是最好的方法。

    这具有额外的好处,您可以传入任何可调用对象,例如 lambda 或 std::function 或仿函数。

    添加完美转发是另一个好主意。可以说,callable 可以被转发为尽可能通用。

    #include <utility>
    
    template<typename F, typename ...Args>
    void execute(F fun, Args&& ...args) {
        fun(std::forward<Args>(args)...);
    }
    

    如果函数的签名很重要,这就是您不想修改 execute 的原因,那么可以使用类型特征从 F 中提取它。

    【讨论】:

      【解决方案4】:

      它不起作用,因为其中一个参数是const&amp; - 你可能已经注意到了。可以通过创建一个包含 const 引用的辅助结构来消除这些关键字:

      #include <iostream>
      #include <string>
      #include <functional> 
      
      template<typename T>
      struct const_ref {
          const_ref(const T& value) : value(value) {}
          const std::reference_wrapper<const T> value;
      };
      
      void fun(const_ref<std::string> text, int a, int b) {
          std::cout << text.value.get() << a + b << std::endl;
      }
      
      template<typename ...Args>
      void execute(void(*fun)(Args...), Args ...args) {
          fun(args...);
      }
      
      void init(const std::string &text, int a, int b) {
          const_ref<std::string> refstring{ text };
          execute(fun, refstring, a, b);
      }
      
      int main() {
          init("Fun: ", 1, 2);
      }
      

      这样extecute() 不会被改变。维护起来也不是太难,因为应该是 const T&amp; 的附加参数可以简单地声明为 const_ref&lt;T&gt;

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-08-28
        • 2020-05-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-09
        • 1970-01-01
        相关资源
        最近更新 更多