使用参考推导模板包中的冲突类型
Deduced conflicting types in template pack with reference
我正在开发具有以下结构的程序:
#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
只能在不破坏依赖此功能的其他项目的情况下进行修改。我没有想过这样的解决方案。
建议:使用两组模板可变参数
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);
}
它不起作用,因为参数之一是 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>
.
我不确定您为什么不想更改 execute
,但我认为将其修改为对可调用对象使用单独的模板参数是最好的方法。
这有一个额外的好处,您可以传入任何可调用对象,例如 lambda 或 std::function
或仿函数。
添加完美转发是一个额外的好主意。可以说也可以转发可调用对象,使其尽可能通用。
#include <utility>
template<typename F, typename ...Args>
void execute(F fun, Args&& ...args) {
fun(std::forward<Args>(args)...);
}
如果函数的签名很重要,这就是您不想修改 execute
的原因,有一些方法可以从 F
中提取具有类型特征的签名。
我正在开发具有以下结构的程序:
#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
只能在不破坏依赖此功能的其他项目的情况下进行修改。我没有想过这样的解决方案。
建议:使用两组模板可变参数
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);
}
它不起作用,因为参数之一是 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>
.
我不确定您为什么不想更改 execute
,但我认为将其修改为对可调用对象使用单独的模板参数是最好的方法。
这有一个额外的好处,您可以传入任何可调用对象,例如 lambda 或 std::function
或仿函数。
添加完美转发是一个额外的好主意。可以说也可以转发可调用对象,使其尽可能通用。
#include <utility>
template<typename F, typename ...Args>
void execute(F fun, Args&& ...args) {
fun(std::forward<Args>(args)...);
}
如果函数的签名很重要,这就是您不想修改 execute
的原因,有一些方法可以从 F
中提取具有类型特征的签名。