为什么 gcc 在这里从回调函数的最后一个参数推断类型?

Why does gcc infer the type from the callback function's last argument here?

我正在尝试制作一个模板函数,它接受一个指向具有任意数量输入的函数的指针,除了它必须有一个 int 作为它的最后一个输入。然后模板函数应该用提供的参数调用这个函数,五个作为最后一个参数。代码会说清楚:

#include <iostream>

template<class A, class ... B>
void passFive(A (*f)(B ..., int n), B ... x) {
        f(x ..., 5);
}

void printStrAndInt(const char *s, int n) {
        std::cout << s << " " << n << "\n";
}

int main() {
        passFive(&printStrAndInt, "pineapple");
        return 0;
}

但是,gcc 不喜欢这样,并给我一个错误和注释:

test.cpp: In function ‘int main()’:
test.cpp:14:39: error: no matching function for call to ‘passFive(void (*)(const char*, int), const char [10])’
  passFive(&printStrAndInt, "pineapple");
                                       ^
test.cpp:4:6: note: candidate: template<class A, class ... B> void passFive(A (*)(B ..., int), B ...)
 void passFive(A (*f)(B ..., int n), B ... x) {
      ^~~~~~~~
test.cpp:4:6: note:   template argument deduction/substitution failed:
test.cpp:14:39: note:   mismatched types ‘int’ and ‘const char*’
  passFive(&printStrAndInt, "pineapple");

所以它推断 B 的类型一次是 const char *(正如预期的那样),还有一次是作为 printStrAndInt 的第二个参数(我确信这一点,因为它与除 int 之外的其他数据类型相同) .作为旁注,如果我像这样将 int 移到前面,它就可以正常工作:

#include <iostream>

template<class A, class ... B>
void passFive(A (*f)(int n, B ...), B ... x) {
        f(5, x ...);
}

void printStrAndInt(int n, const char *s) {
        std::cout << s << " " << n << "\n";
}

int main() {
        passFive(&printStrAndInt, "pineapple");
        return 0;
}

虽然这不是我最终解决方案的选项。我想知道的是为什么它以这种意想不到的方式推断类型以及我应该如何解决这个问题。

问题:只有在最后一个位置时,才能推导可变参数列表。

还要考虑到,当不在最后一个位置时,推导为空。

所以,从

template<class A, class ... B>
void passFive(A (*f)(B ..., int n), B ... x) {
        f(x ..., 5);
}

和通话

passFive(&printStrAndInt, "pineapple");

可变参数 B... 列表必须从 f 签名中推导出为 empy(B... 不在最后位置,后跟 int)和 char const *(或者可能是 char const [10])来自 "pineapple".

为避免这种情况,您可以推断出两个不同的列表并强加(SFINAE 或 static_assert())第二个可变参数列表与第一个列表相同,添加 int.

我的意思是(SFINAE 方式)

template <typename A, typename ... Bs, typename ... Cs>
std::enable_if_t<std::is_same_v<std::tuple<Bs...>,
                                std::tuple<Cs..., int>>>
   passFive(A (*f)(Bs ...), Cs ... x)
 { f(x ..., 5); }

不是一个完美的解决方案:在您的原始代码中,如果 f 函数等待(例如)一个 long 并且在您传递的 x... 参数中,这是一个问题int.

应该更好地检查 Cs... 类型(加上 int)是否不等于但可转换为 Bs...。但是(考虑到最后一个int)我没有看到一个简单而优雅的方法来做到这一点(没有开发一个助手class)。