Luka Aleksić
Luka Aleksić

Reputation: 357

How to detect a pointer to an arithmetic type using type traits and concepts?

How do I write a concept that detects a pointer to an arithmetic type?

template <typename T>
concept arithmetic = std::is_arithmetic<T>::value;

template <typename T>
concept pointer_to_arithmetic = requires (T a) {
    { *a } -> arithmetic;
};

template <typename T>
void fn() {
    printf("fail\n");
}   

template <pointer_to_arithmetic T>
void fn() {
    printf("pass\n");
}   

struct s{};

int main() {
    fn<int>();
    fn<int*>();
    fn<s>();
    fn<s*>();
}

I tried the above and it compiles but doesn't do what it's supposed to.

Expected output is:

fail
pass
fail
fail

Instead I get:

fail
fail
fail
fail

It also doesn't work if I replace *a with a[0].

Upvotes: 4

Views: 181

Answers (3)

For an expression E in a compound requirement, the type constraint predicate is fed decltype((E))1.

decltype encodes the value category of the expression in the type it deduces. Since *p is an lvalue expression. The deduced type is T& for some T.

So you may want to rewrite your pair of concepts as

template <typename T>
concept arithmetic_ref = std::is_arithmetic<std::remove_reference_t<T>>::value;

template <typename T>
concept pointer_to_arithmetic = requires (T a) {
    { *a } -> arithmetic_ref ;
};

The atomic predicate could probably be better named.


Of course, this leaves a couple of questions open. Are you just duck-typing, and so any pointer-like type (even std::optional has operator*) is permissible? Or are you after only fundamental pointer types? How should the concept treat cv-qualified types (it currently doesn't permit them)?

Depending on how you answer those questions, the concept could be tweaked further.

Upvotes: 5

Matzi
Matzi

Reputation: 13925

This should do the trick:

template <typename T>
concept pointer_to_arithmetic = requires (T a) {
    requires std::is_arithmetic_v<std::remove_cvref_t<decltype(*a)>>;
    requires std::is_pointer_v<T>;
};

...

pointer_to_arithmetic<int>          -> false
pointer_to_arithmetic<float>        -> false
pointer_to_arithmetic<std::string*> -> false
pointer_to_arithmetic<int*>         -> true
pointer_to_arithmetic<float*>       -> true

The first line detects any type that can be dereferenced to an arithmetic type. Which might not what you need. So you need to add a second line that detects if T is indeed a pointer type.

Upvotes: 1

Qaz
Qaz

Reputation: 61970

The types given to your arithmetic trait are int& and s&, not int and s. One way you can fix this is to remove the reference from the *a expression:

template<typename T>
auto decay(T&& t) -> std::remove_cvref_t<T> {
    return t;
}

template <typename T>
concept pointer_to_arithmetic = requires (T a) {
    { decay(*a) } -> arithmetic;
};

Another way to go about this is to have arithmetic recognize references to arithmetic types as arithmetic because you can still use the variables in the same way. This is covered in another answer, albeit with a change to the concept name.

Upvotes: 0

Related Questions