0

我需要模板函数,它将调用其他函数,其参数取自std::tuple. 我写了一些代码,可以正确编译:

#include <tuple>

template<typename ...ARGS1, typename ...ARGS2>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &)
{
    //call function
}

int main()
{
    std::tuple<int, bool> tuple1(0, false);
    std::tuple<double, void*, float> tuple2(0.0, nullptr, 0.0f);
    foo(tuple1, tuple2);
    return 0;
}

现在我需要再添加一个参数,它是指向函数的指针:

#include <tuple>

template<typename ...ARGS1, typename ...ARGS2>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &, void (*)(ARGS1..., ARGS2...))
{
    //call function
}

void bar(int, bool, double, void*, float)
{
}

int main()
{
    std::tuple<int, bool> tuple1(0, false);
    std::tuple<double, void*, float> tuple2(0.0, nullptr, 0.0f);
    foo(tuple1, tuple2, &bar);
    return 0;
}

我尝试以许多不同的方式来做,但编译器总是返回template argument deduction/substitution failedinconsistent parameter pack deduction with '' and ''. 我不明白,我的代码有什么问题。编译器输出中没有任何有用的信息。

有人可以帮我正确地写这个吗?

4

2 回答 2

4

由于 clang++ 在这种情况下表现出不同(和奇怪)的行为,我并不完全确定这个答案。但是,我想我可以解释使用 g++ 时发生了什么。

让我们看一下下面的例子:

#include <iostream>

template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...))
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

void bar(int, bool, double, void*, float) {}

int main() {
    foo(&bar);
}

__PRETTY_FUNCTION__是一个非标准宏,我经常将其用作允许打印编译器推断的类型的 hack。在这种情况下,当使用 g++ 编译时,程序会打印:

void foo(void (*)(ARGS1 ..., ARGS2 ...))
[with ARGS1 = {}; ARGS2 = {int, bool, double, void*, float}]

可以看到,g++ 并没有推导出什么 for ,而是推导出了forARGS1函数参数的所有类型。barARGS2


模板参数推导分别适用于每个函数参数。如果模板参数T是从两个函数参数推导出来的,则推导的类型必须相同(几乎没有例外)。同样,对于参数包。考虑:

template<typename T>
void foo(T, T) {}

foo(1, 2.3)

这不会编译,因为对于第一个函数参数,T推断为 be int,但对于第二个函数参数,T推断为 be double

相似地,

template<typename... T>
void foo(tuple<T...>, tuple<T...>) {}

foo(tuple<int, double>{}, tuple<bool, char>{})

T...第一个和第二个函数参数的推导结果不一致。这种不一致会导致编译错误。


正如我们在这个答案的第一部分中看到的那样,对于像这样的参数:

template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...))

ARGS1根本不会被推导出来,并且ARGS2会“吃掉”你传入的参数的所有函数参数类型。如果我们现在ARGS2也从另一个函数参数中推导出:

template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...), tuple<ARGS2...>)

然后,扣除结果ARGS2必须是一致的:

void bar(int, double);
tuple<int, double> t;
foo(&bar, t); // works

tuple<double> u;
foo(&bar, u) // fails: inconsistent deduction results.

使 OP 中的代码编译的一种非常粗略的方法是告诉编译器不要从第三个参数进行推断ARGS2。这可以通过将第三个参数的类型放在非推导上下文(不进行推导的上下文)中来实现:

template<typename T>
struct identity_t
{ using type = T; };

template<typename T>
using non_deduced_context = typename identity_t<T>::type;

template<typename ...ARGS1, typename ...ARGS2>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &,
         non_deduced_context<void (*)(ARGS1..., ARGS2...)>)
{
    //call function
}

对于像some_class_template<T>::nested_type,这样的参数T是在非推导上下文中。这在上面的示例中使用,尽管有一些语法糖。


但是,在 C++ 中将函数传递给另一个函数的常用方法是不同的:

template<typename F>
void foo(F f)
{
    //call function, e.g.
    f(42);
}

这允许传递任意函数对象,包括指向函数的指针。

于 2014-12-30T21:17:34.760 回答
1

我的问题有一个很好的答案,但它消失了。我不知道为什么。

这不是我的想法,但我不记得作者了。

template<typename ...ARGS1, typename ...ARGS2, typename ...ARGS3>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &, void (*)(ARGS3...))
{
    //call function
}
于 2014-12-30T20:59:59.973 回答