我的函数模板有一个函数指针(或成员函数指针)形参。函数指针可以接受任何实参,实参类型被推断出来。在形参列表的末尾,我希望函数模板采用与函数指针形参相同的形参。
template<typename R, typename... Args>
void f(R(*fp)(Args...), Args... args);
当参数类型不完全匹配时,问题就开始了,例如,假设fp
取Base*
,f
的调用者给你Derived*
或fp
取double
,调用者输入0
(类型为int
)。
是否有任何方法告诉编译器仅基于fp
推断Args
和"只是使用"args
的那些推断类型,而不是试图根据它们绑定到的参数匹配它们?
我找到了一个有效的解决方案。我不知道的术语是非演绎的语境。例如,作用域解析操作符::
左边的所有内容都是非推导的。在c++ 20中,您可以使用std::type_identity_t
来人为地创建这样的上下文,在c++ 14和c++ 17中,您可以创建自己的type_identity_t
。
所以你可以用
template<typename R, typename... Args>
void f(R(*fp)(Args...), std::type_identity_t<Args>... args);
关于完全转发,当fp
通过引用或值接受参数时,f
也会这样做。对于std::forward
,按值形参将被移动,引用形参将被引用。
一种选择是为1推导不同的类型,并使用SFINAE或概念在以下情况下禁用过载:
// no SFINAE
template <typename R, typename... Args, typename... UArgs>
void f(R (*fp)(Args...), UArgs&&... args);
// SFINAE
template <typename R, typename... Args, typename... UArgs>
auto f(R (*fp)(Args...), UArgs&&... args)
-> std::void_t<std::invoke_result_t<decltype(fp), UArgs...>>;
// concepts
template <typename R, typename... Args, typename... UArgs>
auto f(R (*fp)(Args...), UArgs&&... args)
requires std::invocable<decltype(fp), UArgs...>;
此选项与std::type_identity
选项的一个区别是,当您调用f
时,需要将参数转换为fp
,例如
struct X { X(int); };
void f1(X);
f(&f1, 33); // implicit conversion from int to X
在std::type_identity
的情况下,转换是在调用方端完成的(即,当您调用f
时),而使用上面的选项,转换是在f
内部调用fp
时完成的。
1您也可以使用泛型函数类型,就像在评论中建议的那样,取决于您在f()
中做什么。