Iter Ator
Iter Ator

Reputation: 9289

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

I would like to define a class, which has one template parameter T. I would like to make sure, that T has a void Update(uint32_t) and a int32_t GetResult() const method. I know that the user will get an error when I try to call one of these methods, but I would like to make sure (possibly with a static assert) that they exists as soon as I can.

I was only able to find solutions to check if the class T is derived from some other class U, but that is not what I want. I wold like to allow any class, which has the mentioned methods.

Upvotes: 4

Views: 1773

Answers (3)

MSalters
MSalters

Reputation: 179799

If you want to use a static_assert, that is possible:

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

The cast checks if overload resolution is possible, i.e. if the right signature is present.

Upvotes: 0

Radosław Cybulski
Radosław Cybulski

Reputation: 2992

You don't need concepts for this, although those help. This code will happily works in 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;
}

Note, that this code checks exact function signature, so Update(int) won't pass.

EDIT: added const to getResult

Upvotes: 5

L. F.
L. F.

Reputation: 20579

With concepts introduced in C++20, this is trivial with requires expressions and requires clauses. In the code, the keyword requires appears twice. The first introduces a requires clause, and the second introduces a requires expression.

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

(live test)

Prior to C++20, you can write a trait class that deploys SFINAE:

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

Now you can use it like:

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

Or:

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

(live test)

GetResult can be handled analogously.


Note: the above code only ensures that the expressions are valid, rather than ensuring the precise signatures. Of course you can also do that. Here's the approach with concepts:

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's answer already shows how to do this without concepts, so I am not showing it again.

Upvotes: 6

Related Questions