如何要求C++模板参数具有具有给定签名的某些方法

How to require a C++ template parameter to have some methods with a given signature?

本文关键字:方法 C++ 参数      更新时间:2023-10-16

我想定义一个类,它有一个模板参数T。我想确保T有一个void Update(uint32_t)和一个int32_t GetResult() const的方法。我知道当我尝试调用这些方法之一时,用户会收到错误,但我想确保(可能使用静态断言(它们尽快存在。

我只能找到解决方案来检查类T是否派生自其他类U,但这不是我想要的。我想允许任何具有上述方法的类。

你不需要这方面的概念,尽管这些概念会有所帮助。这段代码将在 C++11 中愉快地工作。

#include <type_traits>
#include <cstdint>
using namespace std;
struct A {
    void Update(uint32_t);
    int32_t GetResult() const;
};
struct B {
};
using namespace std;
template <typename T, typename = typename enable_if<
    is_same<decltype(&T::Update), void (T::*)(uint32_t)>::value &&
    is_same<decltype(&T::GetResult), int32_t (T::*)() const>::value
    >::type>
struct CLS 
{
};
using namespace std;
int main() {
    CLS<A> a; // works
    CLS<B> b; // fails to compile
    return 0;
}

请注意,此代码检查确切的函数签名,因此Update(int)不会传递。

编辑:添加了常量以获取结果

对于 C++20 中引入的概念,这对于 require 表达式和 require 子句来说是微不足道的。在代码中,关键字 requires 出现两次。第一个引入了 require 子句,第二个引入了 require 表达式。

template <typename T>
    requires requires (T& a, const T& b, uint32_t i) {
        a.Update(i);
        {b.GetResult()} -> int32_t;
    }
void foo(T& x)
{
    // ...
}

(现场测试(

在 C++20 之前,您可以编写一个部署 SFINAE 的 trait 类:

template <typename T>
class foo_traits {
    static auto check(...) -> std::false_type;
    template <typename U>
    static auto check(U x) -> decltype((void)x.Update(uint32_t{}), std::true_type{});
public:
    static constexpr bool has_update = decltype(check(std::declval<T>()))::value;
};

现在你可以像这样使用它:

template <typename T>
void foo(T& x)
{
    static_assert(foo_traits<T>::has_update, "blah blah blah");
}

或:

template <typename T>
std::enable_if_t<foo_traits<T>::has_update> foo(T& x)
{
}

(现场测试(

GetResult可以类似地处理。


注意:上面的代码只确保表达式有效,而不是确保精确的签名。当然,你也可以这样做。以下是概念的方法:

template <typename T>
    requires requires (T& a, const T& b, uint32_t i) {
        a.Update(i);
        {&T::GetResult} -> std::int32_t (T::*)() const;
    }
void foo(T& x)
{
    // ...
}

Radosław Cybulski的回答已经展示了如何在没有概念的情况下做到这一点,所以我不再展示它。

如果你想使用一个static_assert,这是可能的:

static_assert(static_cast< void (T::*)(uint32_t) >(&T::update), 
              "Need void T::update(uint32_t");

强制转换检查是否可以解决重载问题,即是否存在正确的签名。