我试图从 CRTP 基类中推断函数的返回类型和参数,以便“包装”函子。
从基本的 CRTP 模式开始:
template<typename Deriv>
struct Function
{
int func(int a, int b)
{
return static_cast<Deriv*>(this)->func(a, b);
}
};
struct Add : Function<Add>
{
int func(int a, int b)
{
return a + b;
}
};
这可以编译,但当然不是很通用!那么让我们更进一步:
template<typename Deriv, typename R, typename ...Args>
struct Function
{
R func(Args&&... args)
{
return static_cast<Deriv*>(this)->func(std::forward<Args>(args)...);
}
};
struct Add : Function<Add, int, int, int> // R = int, Args... = int, int
{
int func(int a, int b) { return a + b; }
};
但是当指定
R = int
和 Args = int, int
时,这种情况会重复出现。那么我怎样才能推断出这些呢?例如:
template<typename Deriv>
struct Function
{
Helper<Deriv>::R func(Helper<Deriv>::Args ...args)
{
return static_cast<Deriv*>(this)
->func(std::forward<Helper<Deriv>::Args>(args)...);
}
};
struct Add : Function<Add> // Much cleaner
{
int func(int a, int b) { return a + b; }
};
我意识到在实例化 Base 时,
Add::func
的定义是未知的......这就是我想的问题。
我看过这个,里面推荐了traits:How to infer the type in CRTP?
但这意味着我必须在特征中定义
R
/Args...
,然后稍后再次定义该函数。
我正在尝试让事情变得“干”,这样我就不会重复自己,这意味着我只想定义一次
int func(int, a, int b) { return a + b; }
(它的类型/签名)。
在 CRTP 中,
Deriv
是不完整的。
检索 Deriv::func
的签名需要完整的类型。
一种方法是不使用 CRTP 并使用常规层次结构:
template <auto M>
struct Function;
template <typename Base, typename Ret, typename... Args, Ret (Base::*M) (Args...)>
struct Function<M> : Base
{
Ret func(Args... args) {
return (this->*M)(std::forward<Args>(args)...);
}
};
然后
struct Add_Impl
{
int func(int a, int b) { return a + b; }
};
using Add = Function<&Add_Impl::func>;
简而言之,你想要做的事情是不可能的。当CRTP类通过
struct Add : Function<Add>
中的继承实例化时,赋予它的Deriv = Add
是不完整的。无法从中提取出Add::func
的签名等信息。
即使可以,语法
Deriv::R func(Deriv::Args... args)
也会被破坏,因为Deriv
不是模板参数包,因此无法扩展。使用辅助模板Helper<Deriv>::Args
也不起作用。您可以将 Args
存储为 std::tuple
,但使用元组可能不是您想要的。
Function::func
模板如果
Function::func
是成员函数模板,那么您可以省略在签名中指定任何参数。
一切皆可推论。
template<typename Deriv>
struct Function
{
template<class... Args>
requires requires (Deriv& d, Args&&... args) {
// note: if func was a call operator, you could use std::invocable as a
// requirement instead of this
d.func(std::forward<Args>(args)...);
}
decltype(auto) func(Args&&... args)
{
return static_cast<Deriv*>(this)->func(std::forward<Args>(args)...);
}
};
推导了返回类型,并且可以使用任何类型的参数调用该函数,但是它受到限制,因此只能通过
Deriv::func
进行对 Function::func
的有效调用。
明显的缺点是
Function::func
现在是一个模板,并且比 Add::func
复杂得多。
还有其他不好的解决方法,例如使用宏,但它们对于代码质量来说可能更差。
这与基于
Deriv
的类型的情况相同。您必须根据 typename Deriv
实例化第三个模板类型,它将在具体类的专门化中定义 func(例如 Deriv=Add
),并在 Function<Add>
实例化时完成。
此时代码已经足够复杂,应该有一个很好的解释为什么需要它。