1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

C++ 11 nullptr and constexpr

Discussion in 'C++' started by BiplabKamal, Apr 12, 2016.

  1. nullptr



    A C++ programmer frequently come across pointer variables, pointer arguments to function and pointer template arguments. Let us take different cases to see how C++98 handles the situation where a pointer is set to nothing.

    When we write void *p = 0; or void *p = NULL; What do we do? We think that we are initializing the pointer to null pointer but actually we are initializing it with 0 or 0L depending upon the definition of NULL. NULL is not a C++ keyword, it is defined in some header as 0 or 0L. Here is no type safety. This aspects can lead to surprising behavior when you try to pass null pointer to following overloaded functions:
    Code:
    void fn1(bool b) 
    {
        cout << "Function  fn1(bool) " << endl;
    }
    void fn1(int i)
    {
        cout << "Function  fn1(int) " << endl;
    }
    void fn1(int* p)
    {
        cout << "Function  fn1(int*) " << endl;
    }
    
    If you pass 0 or NULL while calling the function like fn1(NULL); it will not call fn1(int* pi) but will call to fn1(int i). So there is no way to call fn1(int* pi) passing a null pointer.

    Adding auto keyword in C++11 and auto return type from a function has made it more difficult to use null pointer as zero or NULL. If you write auto p = NULL;then auto will be deduced to int though your intention is to return a pointer.
    Code:
    auto myfn()
    {
        return NULL; // Will return int
    }
    auto myfn()
    {
        return nullptr; //Will return std::nulptr_t
    }
    
    nullptr in C++ is a new keyword as well as a constant which can not be converted to integral type. The type of nullptr is std::nulptr_t which also can be converted to any pointer or pointer to member. You may surprise how it is a key word and same time it is an instance of a type. Think about keywords ‘true’ and ‘false’, they are also of type bool.

    Now if we call fn1(nullptr) it will call fn1(int*)

    std::nullptr_t is the type of null pointer literal nullptr. It seems to be cyclic type i.e. nullptr is type of std::nullptr_t and std::nullptr_t is of type nullptr .
    Code:
    Std::nullptr_t is defined as-
    typedef decltype(nullptr) nullptr_t; 
    
    Following example shows how nullptr, std::nullptr_t and other type of pointers are co-related-
    Code:
    #include<iostream>
    using namespace std;
    
    void fn(nullptr_t np) 
    {
        cout << "Function  fn(nullptr_t np) " << endl;
    }
    void fn(void* pv)
    {
        cout << "Function fn(void* pv) " << endl;
    }
    void fn(int* pi)
    {
        cout << "Function  fn(int* pi) " << endl;
    }
    
    int main()
    {
        int* pi = nullptr; char* pd = nullptr;
        fn(pi); // Will call fn(int* pi)
        fn(pd);// Will call fn(void* pv)
        fn(nullptr);  //Will call fn(nullptr_t np). if std::nullptr_t version not present it     will be ambiguous
        fn(NULL);  // ambiguous call: all three functions are equally matching
        return 0;
    }
    
    std::nullptr_t will be converted to any type of pointer. When a 0 or NULL is passed where a pointer is expected the compiler will treat them as null pointer but that’s a fallback position. In general the zero is considered as an integer, not a pointer. On the other hand nullptr is considered as a pointer, not an integer. So when some one call a function with NULL like fn(NULL); above he wants to mean that he is calling the function with null pointer but he actually mean that he is calling the function with some kind of integer.

    In case of a normal function you can pass 0 or NULL where a pointer is expected and it will work, but in case of template functions there are scenarios where you can not pass 0 or NULL. Suppose you have a series of functions which differs with it’s argument and the argument is different kind of pointer. Now you define template function which do some common work prior to call the function. Template arguments specify the type of function and type of argument. When you call the template function using automatic type deduction it stops you from passing 0; Here is the example code:

    Code:
    #include<iostream>
    using namespace std;
    
    void fn1(char* pc)
    {
        cout << "Function fn(char*) " << endl;
    }
    void fn2(int *pi)
    {
        cout << "Function  fn(int*) " << endl;
    }
    void fn3(long *pi)
    {
        cout << "Function  fn(long*) " << endl;
    }
    template<class functype, class pointertype>
    auto CallFunction(functype f, pointertype p)
    {
        // code to do some preperation before calling the function
        return f(p);
    };
    
    int main()
    {
        int i{ 100 };
        char c{ 'a' };
        CallFunction(fn1, &c); // pointer type will be deduced to char*
        //CallFunction(fn1, 0);// Will generate compiler error because  pointer type will be deduced to int
        CallFunction(fn1, nullptr); //pointer type will be deduced to char*
    
        CallFunction(fn2, &i); //pointer type will be deduced to int*
        //CallFunction(fn2, 0); // Will generate compiler error because  pointer type will be deduced  int
        CallFunction(fn2, nullptr); //pointer type will be deduced to int*
        return 0;
    }
    

    Constexpr



    We used to use macros and constant variables to define same literals to be used in multiple times or make it more readable. For example following code uses a macros and constant variable:
    Code:
    #include<iostream>
    using namespace std;
    #define SQUARE(n) n*n //macro
    int main()
    {
        int n{ 0 };
        cout << "Enter the number: ";
        cin >> n;
        const int sq = SQUARE(n); //run time initialization of a constant data
        cout << "Square of "<<n<<" is: " << sq<<endl;
        return 0;
    }
    
    You know macro is replaced before compilation and no type safety. Whereas the constant object can be initialized at compile time by a literal or can be initialized at run time by using run time value. For example
    Code:
    const int sq = SQUARE(n); // Is run time initialization because n is not a constant
    const int sq = SQUARE(12);// Is compile time initialization
    
    While macro lacks the type safety, constant variable is not guaranteed to be constant expression due to run time initialization. Also when ‘const’ key word is applied to member function it has different meaning that the function cannot modify the object. On the other hand if an object is declared as constant it means you cannot call a non-constant member function of the object. So by making an object constant you promise that the object will not be modified and by making a member function constant you promise that the function will not modify the underlying object. Now C++11 introduces a new key word ‘constexpr’ which have the strong type safety, has same meaning when applied for both object and functions (both member and non-member) and guarantee to be a constant expression which can be resolved at compile time. This enables a function or an object to be used in constant expression. There are language rules where constant expressions are required. For example: array bounds, case labels and some template arguments.

    For variable or object, constexpr implies constant and for function it implies that the result of the function can be evaluated at compile time. So both object and function declared with ‘constexpr’ key word can be use as a constant expression wherever applicable. Note that an object declared as constexpr is bound to be constant but an object declared as const need not to be a constexpr. To be constexpr, an expression should be possible to resolve at compile time. Loot at following examples of constant and constant expression variables:
    Code:
        int i=10;// non constant
        const int ci = 10; //constant with compile time initialization
        const int ci2 = i; //constant with run-time initialization
        constexpr int cei = i; //Error because i is not a constant
        constexpr int cei = 10;// constant expression
    
        int ar1[ci];//array bound is a constant expression and resolved at compile time
        int ar1[ci2]; //Error because ci2 is not a constant expression
        int ar2[cei];//cei is a constant expression and resolved at compile time 
    
    Example of constexpr function
    Code:
    constexpr int cfn(int i)
    {
        return i*i;
    }
    
    int arr[cfn(10)];
    
    A function which is declared with ‘constexpr’ key word have lot of restriction. It cannot do anything which cannot be resolved at compile time. It cannot declare a variable or modify the argument, return type and arguments should be literal type. It should be non-virtual, and should have one and only one return statement. Return type cannot be of void type. It can have typedef or static assert statements but not other kind of statements. For Example-
    Code:
    constexpr int cfn(int i)
    {
        using a = decltype(i); //allowed 
        int j = 0; // not allowed
        i = 10;//Not allowed
        constexpr int k = 10;//not allowed
        const int l = 0;// Not allowed
        return i*i * 10;
    }
    
    Example for class member as constant expression:
    Code:
    class MyClass
    {
    private:
        int data;
    public:
        constexpr MyClass(int val) :data{val }
        {
            data = val;//Error
        }
        constexpr int GetData() 
        {
            data = data * 10;//Error
            return data*10;
        }
        void SetData(int val)
        {
            data = val;
        }
    };
    
    Following function can not be declared as constexpr because both return type and the argument are not literal type
    Code:
    constexpr list<int> cfn2(list<int> l)
    {
        return l;
    }
    
    Declaring an object of a user defined type as constexpr puts restriction on type definition. For example if you declare a class ‘MyClass’ and want to create an instance of the class like:constexpr MyClass obj; then your class should have a constructor which is constexpr. Following example shows the class eligible for constexpr instantiation-

    Code:
    class MyClass
    {
    private:
        int data;
    public:
        constexpr MyClass():data{0}
        {
            
        }
        constexpr MyClass(int val):data{val }
        {
            //data = val;//Error
        }
        int GetData() const
        {
            return data;
        }
        void SetData(int val)
        {
            data = val;
        }
    };
    
    int main()
    {
        constexpr MyClass obj;
        int i = 0;
        const MyClass obj1(i);//Constant object
        obj1.GetData();//OK
        obj1.SetData();//Error:Cannot call non-constant member
        constexpr MyClass obj2(i);// Error: Can not pass a variable
        constexpr MyClass obj3(0);//OK as parameter is a constat
        obj3.GetData();//OK
        obj3.SetData();//Error:Cannot call non-constant member
    
        return 0;
    }
    
    The most important use of constexpr key word for a function. A constant expression function with arguments can be used in two purposes- as a constant expression when a constant value is passed and as a normal function when a run-time value is passed.
     

Share This Page