使用 C++ CRTP,如何推断派生类中函数的参数?

With C++ CRTP, how do I infer the parameters of a function in the derived class?

提问人:user5406764 提问时间:8/17/2023 最后编辑:Jan Schultkeuser5406764 更新时间:8/17/2023 访问量:147

问:

我试图从 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 = intArgs = 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 中推断类型?

但这意味着我必须在特征中定义 /,然后稍后再次定义函数。RArgs...

我试图让事情变得“干燥”,这样我就不会重复自己,这意味着我只想定义一次(它的类型/签名)。int func(int, a, int b) { return a + b; }

C++ 模板 variadic-templates 函子 CRTP

评论

1赞 Öö Tiib 8/17/2023
请注意,您的任何示例都不会编译,因此可能会造成混淆。
0赞 463035818_is_not_an_ai 8/17/2023
“我看过这个,它推荐了特征”你链接的问题没有被接受的答案,甚至没有“最高答案”,因为他们都有 0 票
1赞 463035818_is_not_an_ai 8/17/2023
“但这意味着我必须定义R/Args......在一个特征中,然后稍后再次定义函数。我不明白你看到的问题。该特征不是使用明确的专用化,您需要列出每个可能的所有特征,但该特征只是从任何给定类型推断。没有重复R/ArgsBaseR/Args
0赞 Jarod42 8/17/2023
@463035818_is_not_an_ai:请看这里。两者都有 和 .using type = int(int, int);int func(int, int)
1赞 Jarod42 8/18/2023
@WeijunZhou:“推断这一点”可以摆脱一些 CRTP。很难提出一些有用的东西,因为 OP 示例过于简化并且没有添加任何内容......func

答:

3赞 Jan Schultke 8/17/2023 #1

简而言之,你要做的事情是不可能的。当 CRTP 类通过继承实例化时,给定给它的是不完整的。无法从中提取任何信息,例如签名。struct Add : Function<Add>Deriv = AddAdd::func

即使可以,语法也会被破坏,因为不是模板参数包,所以无法扩展。使用帮助程序模板也无法正常工作。您可以存储为 ,但使用元组可能不是您想要的。Deriv::R func(Deriv::Args... args)DerivHelper<Deriv>::ArgsArgsstd::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::funcFunction::func

明显的缺点是它现在是一个模板,并且比 .Function::funcAdd::func

还有其他不好的解决方法,例如使用宏,但它们可以说对代码质量来说更糟。

评论

0赞 n. m. could be an AI 8/17/2023
这还不错。无论如何都不比差。std::function
0赞 Swift - Friday Pie 8/17/2023
@n.m.couldbeanAI 是我看到的一个实现,它使用了某种带有帮助程序类的 CRTP,该类依赖于可调用商店的哪个孩子。这允许以牺牲compexity为代价解决端鸡问题。std::function
1赞 n. m. could be an AI 8/17/2023
@Swift-FridayPie 对不起,我想我太快了,无法将其归纳为 std::function。它更像是 std::thread 构造函数等。你会得到一个可调用对象和一个参数列表,但没有办法从可调用对象派生参数,因此它们必须保持泛型。
0赞 Swift - Friday Pie 8/18/2023
@n.m.couldbeanAI 是的,宏方法或 mking 它绝对很糟糕,我看到,人们只能尝试为用户提供选择正确的参数:stackoverflow.com/questions/76741051/......
4赞 Jarod42 8/17/2023 #2

在 CRTP 中,是不完整的。 检索 的签名需要完整的类型。DerivDeriv::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>;

演示

0赞 Swift - Friday Pie 8/17/2023 #3

这与基于 的类型相同。您必须根据 实例化第三个模板类型,该模板类型将在具体类的专用化中定义 func(例如 ),并在实例化 的那一刻完成。Derivtypename DerivDeriv=AddFunction<Add>

在这一点上,代码已经足够了,应该有一个很好的解释为什么需要它。