如何让C++编译器间接推导 T

How can I get the C++ compiler to deduce T indirectly?

本文关键字:编译器 C++      更新时间:2023-10-16

我的模板很弱。我有这个代码:

template<typename T>
void Foo(void(*func)(T*)) { }
void Callback(int* data) { }
int Test()
{
    Foo(Callback);
}

。但我想要比 C 讨厌的函数指针语法更具可读性的东西 void(*func)(T*).

我的团队中有人建议:

template<typename T>
struct Types
{
    typedef void Func(T*);
};
template<typename T>
void Foo2(typename Types<T>::Func* func) {}
void Test2()
{
    Foo2(Callback);        // could not deduce template argument for 'T'
    Foo2<int>(Callback);   // ok
}

(我仍在争论这是否真的更具可读性,但这是一个单独的问题。

如何帮助编译器找出 T 是什么,而无需在调用方中显式指定它?

您可以使用

特征类从函数类型中提取T

template<class F>
struct CallbackTraits;
template<class T>
struct CallbackTraits<void(*)(T)>
{
    typedef T ArgumentType;
};

您的示例可以像这样修改:

template<typename F>
void Foo(F func)
{
    typedef typename CallbackTraits<F>::ArgumentType T;
}
void Callback(int* data) { }
int Test()
{
    Foo(Callback);
}

此技术用于提升类型特征库:http://www.boost.org/doc/libs/1_57_0/libs/type_traits/doc/html/boost_typetraits/reference/function_traits.html

这篇博文详细介绍了该技术的实现:https://functionalcpp.wordpress.com/2013/08/05/function-traits/

不幸的是,这种方法隐藏了Foo签名中有关传入参数约束的信息。在上面的示例中,参数必须是类型 void(T*) 的函数。

此替代语法与原始示例相同,但可读性略高:

template<typename T>
void Foo(void func(T*)) { }

另一种可能更具可读性的替代语法可以使用 c++11 的别名模板实现,如下所示:

template<typename T>
using Identity = T;
template<typename T>
void Foo(Identity<void(T*)> func) { }

不幸的是,最新的 MSVC 无法编译它,报告了内部编译器错误。

您将无法根据嵌套名称推断类型:没有理由为什么外部类型的不同实例不会定义相同的内部类型。不过,您可以使用using别名:

template <typename T>
using Function = auto (*)(T*) -> void;
template <typename T>
void Foo(Function<T>) {
}

就个人而言,我建议不要使用其中任何一个:在实践中,实际采用一个函数对象似乎更可取,该对象稍后允许使用具有合适函数调用运算符的对象。对于回调,通常需要传入一些辅助数据。也就是说,您可以使用不受约束的模板或采用类型擦除类型的模板,具体取决于您要执行的操作:

template <typename Fun>
void Unconstrained(Fun fun) {
}
template <typename T>
void TypeErased(std::function<void(T*)> fun) {
}

不受约束的版本的优点是它可能会内联函数调用,但它的缺点是每个函数对象类型都会创建一个新的实例化,并且参数类型可能会有所不同。类型擦除版本实际上必须执行类似于虚拟函数调用的操作,但函数模板只有一个实例化(当然,每个参数类型T(。

诚然,类型擦除版本的类型不会从函数指针(或任何其他不是std::function<void(X*)>的参数(中推断出来,即您可能希望有一个转发函数

template <typename T>
void TypeErased(Function<T> fun) {
    TypeErased(std::function<void(T)>(fun));
}

在 C++98 和 C++03 中,模板参数推导仅适用于函数(和方法(。

我不认为在最近的标准中情况发生了变化。