通过参考推断模板包中的冲突类型

Deduced conflicting types in template pack with reference

本文关键字:包中 冲突 类型 参考      更新时间:2023-10-16

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

#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)

不得更改。如何解决此问题,以便通过引用传递textexecute不会更改,如果可能的话也不会更改init

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

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

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

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

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

假设您有接收long的函数foo()

void foo (long)
 { }

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

execute(foo, 1);

如果您使用单个Args...可变参数序列,则调用将失败,就像您的问题一样,因为编译器推断Args... long(从foo()签名(和long(从值1(,因此存在歧义。

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

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

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

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

这还有一个额外的好处,你可以传入任何可调用对象,如 lambda 或 std::function 或函子。

添加完美的转发是一个额外的好主意。可以说,可调用对象也可以转发以尽可能通用。

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

如果函数的签名很重要,这就是为什么你不想修改execute有一些方法可以从具有类型特征的F中提取它。

它不起作用,因为其中一个参数是const& - 正如您可能已经注意到的那样。可以通过创建包含 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&的其他参数可以简单地声明const_ref<T>