带有参考的模板包中推导的冲突类型

时间:2019-03-29 09:52:29

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

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

#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只能在不依赖此功能的其他项目中断的范围内进行修改。我没有想到这样的解决方案。

4 个答案:

答案 0 :(得分:4)

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

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()签名)和{{ 1}}(来自值long),因此模棱两可。

如果使用两个可变参数序列,则编译器为1推论long,为As1...推论int,没有歧义,并且As2...传递了一个为需要execute()值的函数提供int值,这是完全合法的。

答案 1 :(得分:3)

我认为您无需更改execute,而无需触摸init()。一种方法是显式传递模板参数(绕过参数推导以便传输引用类型信息):

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

答案 2 :(得分:3)

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

这具有附加的优点,您可以传递任何可调用对象,例如lambda或std::function或函子。

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

#include <utility>

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

如果函数的签名很重要,这就是为什么您不想修改execute的原因,那么有多种方法可以从具有特征类型的F中提取出来。

答案 3 :(得分:1)

它不起作用,因为其中一个参数为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>