Search code examples
c++c++20c++-concepts

C++ concepts for numeric types and preventing instantiation on pointers


I'm trying to write a concept that instantiates a function if the template parameter is only of numeric type (integers, floats, doubles and so on) and throw an error if the type is a pointer type or a boolean type.

#include <iostream>
#include <type_traits>
#include <boost/type_index.hpp>

template<typename T>
concept NumericType = requires(T param)
{
    {std::is_integral_v<T> || std::is_floating_point_v<T>};
    {!std::is_same_v<bool, T>};
    {std::is_arithmetic_v<decltype(param +1)>};
    !std::is_pointer_v<T>;
};

But when I try to use it with pointer to int or float or double, the compiler instantiates the function and adds one to the pointer which works because of pointer arithmetic.

template<NumericType T>
T add_one(const T val)
{
    return val + 1;
}

int main(){
    int two{2};
    int* pTwo = &two;
    float three{3.1415};


    std::cout << "add_one(two):  " << add_one(two) << "\n";
    std::cout << "add_one(&two): " << add_one(&two) << "\n";
    std::cout << "add_one(pTwo): " << add_one(pTwo) << "\n";
    std::cout << "add_one(&three): " << add_one(&three) << "\n";
    std::cout << "add_one(true):  " << add_one(true) << "\n";

    return 0;

}

I was expecting the compiler to throw error for the cases where pointer or boolean types passed to add_one function, but surprisingly it doesn't

I fail to understand what what is wrong with the definition of concept NumericType since it contains a clause within the body of the concept definition that hints the compiler that type T should not be of pointer or boolean type using the type trait std::pointer_v<T> `!std::is_same_v<T, bool>. Here's a working example on compiler explorer.


Solution

  • The requires clause only checks the validity of the expression and does not evaluate the value, you need to use nested requires:

    template<typename T>
    concept NumericType = requires(T param)
    {
        requires std::is_integral_v<T> || std::is_floating_point_v<T>;
        requires !std::is_same_v<bool, T>;
        requires std::is_arithmetic_v<decltype(param +1)>;
        requires !std::is_pointer_v<T>;
    };