SFINAE 用于根据函数的参数类型构造具有函数类型模板参数的类

SFINAE for constructing classes with function-type template parameter, based on the type of parameter for the function

提问人:NetoBF 提问时间:11/8/2023 最后编辑:NetoBF 更新时间:11/9/2023 访问量:70

问:

用一个朗朗上口的标题来总结这个话题并不容易。

我的问题如下: 我想调用create-function的不同实现,它为我返回一个指向被调用方的指针,该被调用方使用function-type-template-parameter进行实例化。必须根据 function-type-parameter-types 的返回类型和参数类型的type_traits来选择这些实现。

我有带有function-type-template-parameters的类(我希望这是该参数的正确表达式)。这些是虚拟类,它们可以有不同的实现,其中 call() 函数的实现方式不同。 因此,基类如下所示:

template <typename T> class Callee;

template <typename TReturn, typename... TParams> class Callee<TReturn(TParams...)>
{
public:
    Callee() {}

    virtual TReturn call(TParams... data) = 0;
};

创建的派生对象在某处注册,然后通过标识符传递指向创建的类的指针。 为了实现这一点并使其更容易用于开发人员,我创建了一个“Creator”类,它有一个静态成员函数,该函数调用“内部”函数来获取 Callee 实例。Callee<TReturn(TParams...)>*

所以现在我的观点是,我们需要根据传递的函数类型的参数类型来创建东西的不同实现。 我已经设法实施了参数包的类型是否包含引用的检查,但除了使用额外的 bool-template 参数外,我没有发现其他解决方案。这目前用于两者,也不是干净的解决方案。 所以现在它基本上看起来像这样:TReturnTParams...

template <typename... TParams> class CallerCreator; 

template <typename T, bool containsRef = false> static Callee<T>* createInternal(uint32_t id)
{
    Callee<T>* callee
        = reinterpret_cast<Callee<T>*>(searchCallee(id));

    if (callee == nullptr)
    {
        callee = new OtherCallee<T, containsRef>(id);
    }
    return callee;
}

template <typename TReturn, typename... TParams>
class CallerCreator<TReturn(TParams...)> final
{
public:
    static Callee<TReturn(TParams...)>* create(uint32_t id)
    {
        return createInternal<TReturn(TParams...), contains_reference<TParams...>::value || std::is_reference<TReturn>::value>(id);
    }
};

为了完整起见,“contains_reference”实现(我们使用的是C++14...),它基于: 检查参数包是否包含类型

template <typename... List> struct contains_reference : std::true_type
{
};

template <typename Head, typename... Rest>
struct contains_reference<Head, Rest...>
    : std::conditional<std::is_reference<Head>::value, std::true_type, contains_reference<Rest...>>::type
{
};

template <typename Tp> struct contains_reference<Tp> : std::false_type
{
};

我也为contains_non_trivially_copyable和contains_non_standard_layout这样做了。

正如您在代码片段中看到的,如果没有找到 Callee,则创建该代码段,我将 bool 转发到此类,因为根据类型是否包含引用,可以有不同的实现。 请参阅我用来“禁止”创建此内容的静态断言。 但这不是我想要的。我只希望 just 不分配这个对象。只需将实现视为完整性的附加信息,但它与问题无关。 想象一下:OtherCallee<T, containsRef>OtherCalleecreateInternalOtherCallee

template <typename T, bool containsRef = false> class OtherCallee;
template <typename TReturn, typename... TParams>
class OtherCallee<TReturn(TParams...), false> : public Callee<TReturn(TParams...)>
{
public:
    explicit OtherCallee(uint32_t id)
    {
        // static_assert(not contains_non_standard_layout<TParams...>::value, "You cannot use non-pod-types!");
        // static_assert(not contains_non_trivially_copyable<TParams...>::value, "You cannot use non-pod-types!");

        // static_assert(std::is_standard_layout<TReturn>::value, "You cannot use non-pod-types!");
        // static_assert(std::is_trivially_copyable<TReturn>::value, "You cannot use non-pod-types!");
    }
    TReturn call(TParams... data) override
    { /* stuff done when no refs */ }
};

template <typename TReturn, typename... TParams>
class OtherCallee<TReturn(TParams...), true> : public Callee<TReturn(TParams...)>
{
public:
    explicit OtherCallee(uint32_t id)
    {
    }
    TReturn call(TParams... data) override
    { /*stuff done when refs (e.g. remove them) */}
}

再说一遍:想象一下所有其他专业。

我的问题是,我只是不想在里面创建任何这种情况。OtherCallleestatic_assert

不知何故,我需要根据参数类型对函数进行不同的实现。 所以另一个应该看起来像这样:createInternalcreateInternal

template <typename T, bool containsRef = false /*, bool returnValIsPod */> static Callee<T>* createInternal(uint32_t id)
{
    Callee<T>* callee
        = reinterpret_cast<Callee<T>*>(searchCallee(id));
    return callee;
}

我真的不知道我该如何做到这一点。我尝试直接传递 and 以使用完整的专用化,所以我可能会使用,但我不知道如何正确实现这一点,因为我看不到我如何区分是否以及 目标是我可以在 s 中发表评论,但它们永远不会抛出,因为在非 pod-type for 或的情况下不会调用构造函数的实现。TReturnTParams...enable_ifTReturnvoidTParams...static_assertTReturnTParams...

我添加了一个抛出断言的最小示例:https://godbolt.org/z/47oedPsq7

也许它更容易,我只是目前不明白。

C++ 模板 C++14

评论

0赞 463035818_is_not_an_ai 11/8/2023
目标是什么?具体问题是什么?对不起,我确实阅读了这个问题,但我既没有看到大局,也没有看到您当前试图解决的一个细节。我的建议是减少文本,增加可重复的示例,并包括一个实际问题。
0赞 HolyBlackCat 11/8/2023
相同。为什么有参考参数很重要?你只想在 中,这将确定是否有来自 中的成员变量的引用,或者我为函数类型编写一个单独的 traits 类。if constexprcreateInternalstatic constexpr boolCallee<T>
0赞 NetoBF 11/8/2023
@463035818_is_not_an_ai我添加了一个最小的示例。我在评论中澄清说,这是关于这个函数的,如果参数和返回类型是非 pod,则需要有一个额外的实现。不知道我能更清楚地表达这个问题,对不起:/createInternal
1赞 Yakk - Adam Nevraumont 11/8/2023
此外,“想象一下空洞类型等的所有其他专业化”是一个危险信号。void 类型不应有专用化,尤其是在接口定义中。编写一个好的模板通常是让你的代码统一工作,而不管类型是什么。您似乎希望根据 void/reference/etc 细节来分叉您的实现,在许多情况下,这些细节不应该导致分叉。而且你不清楚在那些分叉的实现中到底应该发生什么,这实际上可以改变分叉应该如何完成。
1赞 Yakk - Adam Nevraumont 11/9/2023
哦等等,我想我看到了你的问题。 摆脱语法问题,并将其分派给两个不同的实现。createInternal<TReturn(TParams...), contains_reference<TParams...>::value || std::is_reference<TReturn>::value>(id)

答:

1赞 Yakk - Adam Nevraumont 11/9/2023 #1

使用 的重载。createInternal

template <typename T> static Callee<T>* createInternal(
  std::false_type containsRef, 
  uint32_t id
)
{
  Callee<T>* callee
    = reinterpret_cast<Callee<T>*>(searchCallee(id));

  if (callee == nullptr) {
    callee = new OtherCallee<T, containsRef>(id);
  }
  return callee;
}

template <typename T> static Callee<T>* createInternal(
  std::true_type containsRef, 
  uint32_t id
)
{
  Callee<T>* callee
    = reinterpret_cast<Callee<T>*>(searchCallee(id));

  if (callee == nullptr) {
    // callee = new OtherCallee<T, containsRef>(id);
    // whatever you do when containsRef is true
  }
  return callee;
}

然后在创建时:

template <typename TReturn, typename... TParams>
class CallerCreator<TReturn(TParams...)> final {
public:
  static Callee<TReturn(TParams...)>* create(uint32_t id) {
    std::integral_constant<bool, contains_reference<TParams...>::value || std::is_reference<TReturn>::value>(id)> containsRef;

    return createInternal<TReturn(TParams...)>(containsRef, id);
  }
};

模板函数不支持模板部分专用化,但支持重载。

因此,我们将 填充到函数参数中并传入。调用每个重载或匹配项。containsRefstd::true_typestd::false_typecontainsRef

(std::true_type是 的别名。std::integral_constant<bool, true>

如果您以后需要将其传递给非类型模板参数,则可以将其用作编译时值,就像我上面所做的那样,但我怀疑您不必在这里使用。std::integral_constant

...

您还可以执行另一个技巧,基本上是 向后移植 ,您可以在其中实例化并运行基于或参数的两个 lambda 之一。if constexprtrue_typefalse_type

但你可能不需要这种技术。

评论

0赞 NetoBF 11/9/2023
我投了赞成票,因为这个答案真的很有用。我不知道 std::false_type 和 std::true_type 以这种方式使用。这似乎解决了我根据类型特征、引用等区分函数重载的基本问题。这并不完全是解决我关于这种非豆粒类型的问题的方法,但我想我看到了如何在这里创建一个解决方案。下次我可能会更新这个话题。非常感谢!