提问人:user5406764 提问时间:8/17/2023 最后编辑:Jan Schultkeuser5406764 更新时间:8/17/2023 访问量:147
使用 C++ CRTP,如何推断派生类中函数的参数?
With C++ CRTP, how do I infer the parameters of a function in the derived class?
问:
我试图从 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
我看过这个,它推荐了特征:如何在 CRTP 中推断类型?
但这意味着我必须在特征中定义 /,然后稍后再次定义函数。R
Args...
我试图让事情变得“干燥”,这样我就不会重复自己,这意味着我只想定义一次(它的类型/签名)。int func(int, a, int b) { return a + b; }
答:
简而言之,你要做的事情是不可能的。当 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
还有其他不好的解决方法,例如使用宏,但它们可以说对代码质量来说更糟。
评论
std::function
std::function
在 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>;
这与基于 的类型相同。您必须根据 实例化第三个模板类型,该模板类型将在具体类的专用化中定义 func(例如 ),并在实例化 的那一刻完成。Deriv
typename Deriv
Deriv=Add
Function<Add>
在这一点上,代码已经足够了,应该有一个很好的解释为什么需要它。
评论
R/Args
Base
R/Args
using type = int(int, int);
int func(int, int)
func