Search code examples
c++functionbuilt-in

How to add member function for built-in types such as integer in C++


I'm implementing something by using C++, I have a cluster of type index, it includes two members: one is an integer, the other is a struct, like this:

typedef int IndexA_t;
struct IndexB_t { ... };

Here comes a requirement, I want to add a common function (suppose, name it fun1()) for these two types as I want to pass them in a template function which will call fun1(), it's easy to achieve this for IndexB_t, but it puzzles me for IndexA_t, how can I introduce a function member for an integer (built-in) type? Shall I have to implement a class simulating int type? it really needs lots of unnecessary work (many operators...), is there any other way?

Very appreciated for your help, thanks.


Solution

  • You could do as cornstalks suggests and use the same technique employed by std::begin and std::end. It provides overloads for objects that define begin/end member functions and provides overloads for further types (in that case arrays) which it makes sense to use the function on.

    In the below contrived example, we have a getval free function, which provides overloads for types that define a getval member function, using the trailing return type to activate SFINAE. Then for other types it uses type traits to ensure that only types which are integers are allowed and all others rejected, again using SFINAE via enable_if.

    This example is contrived and is only intended to show the technique, which you can employ to implement whichever functionality you require (the question does not elaborate what that is, so I had to use license). It would clearly be easier in the below case to achieve the same effect by adding an int conversion operator to the IndexB_t class, but that's not the point.

    #include <iostream>
    #include <type_traits>
    using namespace std;
    
    /* is_integer type trait */
    template<typename T>
    using is_integer = is_same<remove_cv_t<T>, int>;
    
    template<typename T>
    constexpr bool is_integer_v = is_integer<T>::value;
    
    /* getval function definition */
    template<typename T, 
             typename = enable_if_t<is_integer_v<T>>
            >
    T getval(T i)
    { return i; }
    
    template<typename T>
    auto getval(T& t) -> decltype(t.getval())
    { return t.getval(); }
    
    /* defined types */
    typedef int IndexA_t;
    
    class IndexB_t 
    { 
        int x;
    
        public:
        IndexB_t(int i) : x(i) {}
    
        int getval() const { return x; }
    };
    
    // a test struct with no `getval` member function.
    struct NoGetVal {};
    
    // driver function
    int main()
    {
        int i = 9;
        IndexB_t j = 10;
        IndexA_t k = 11;
        const int l = 12;
        volatile int m = 13;
        const volatile int n = 14;
        float a = 1.1;
        NoGetVal ngv;
    
        cout << getval(i) << '\n';
        cout << getval(j) << '\n';
        cout << getval(k) << '\n';
        cout << getval(l) << '\n';
        cout << getval(m) << '\n';
        cout << getval(n) << '\n';
    
        // disallowed as not int
        //cout << getval(a) << '\n';
    
        // disallowed no `getval` member function
        //cout << getval(ngv) << '\n';
    }
    

    So now you have a consistent interface for integer primitives and objects that act like integers.

    You may want to lessen the restriction on which types are allowed in the primitive type overload, perhaps using std::is_integral. It's up to you.

    This is c++14 but could be stepped down to c++11 by using enable_if instead of enable_if_t et al. and adapting the code accordingly.