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

问题描述 投票:0回答:3

我试图从 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; }
(它的类型/签名)。

c++ templates variadic-templates functor crtp
3个回答
3
投票

在 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>;

演示


2
投票

简而言之,你想要做的事情是不可能的。当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
复杂得多。

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


0
投票

这与基于

Deriv
的类型的情况相同。您必须根据
typename Deriv
实例化第三个模板类型,它将在具体类的专门化中定义 func(例如
Deriv=Add
),并在
Function<Add>
实例化时完成。

此时代码已经足够复杂,应该有一个很好的解释为什么需要它。

© www.soinside.com 2019 - 2024. All rights reserved.