Search code examples
c++compilationconstexprcompile-time-constant

Force a constexpr function to be compiled at compile time, even if calculation inside contains a non-const array, by making the returned obj constant?


I read about constexpr functions and when they do a compile time calculation. Now i am at a point, where i have to fill an array with new values, thus the array cannot be const.

From what i know, a constexpr function is definitely evaluated at compile time, when every value inside the function is const and used functions are constexpr which also only use const values. Furthermore, an other post stated, that it's possible to force a compile time compilation by making the returned obj const.

Lets say, my function is a constexpr, but has a non-const array inside, but the returned object will be const. Will my function calculation be evaluated at compile time(forced?).

template<int Size, typename T>
struct Array{
    T array[Size];
    Array(const T * a){
        for(int i = 0; i < Size; i++){
            array[i] = a[i];
        }
    }
};

template<typename T, int size>
class Example{

private:
    Array<size, T> _array;
    public:
        constexpr explicit Example(T * arr):_array(arr){};
        constexpr explicit Example(const T * arr):_array(arr){};
};

template<typename D, int size, typename ...buf, typename T>
constexpr auto calculations(const T & myObj){
    D test1[2];
    // calculation fills arr
    return Example<D, size>(test1);
}

int main(){
    const int size = 2;
    const double test1[size] = {1,2};
    const auto obj1 = Example<double, size>(test1); //compile time
    //obj2 calculations during compile time or run-time?
    const auto obj2 = calculations<double, size>(obj1);
}

Solution

  • From what i know, a constexpr function is definitely evaluated at compile time, when every value inside the function is const and used functions are constexpr which also only use const values. Furthermore, an other post stated, that it's possible to force a compile time compilation by making the returned obj const.

    All of these statements are wrong. See below.


    No, a call to a constexpr function is only guaranteed to be evaluated at compile-time if it is called in a context requiring a (compile-time) constant expression. The initializer of obj2 is not such a context, even if it is const.

    You can force the initializer to be compile-time evaluated by declaring obj2 as constexpr. (Which however has very different meaning than const!)

    Even then it is not going to work, because calculations<double, size>(obj1) is not actually a constant expression. obj1 is not a compile-time constant without declaring it constexpr as well. Similarly this doesn't work because test1 is not a constant expression without declaring it constexpr as well.

    Then you also need to make the constructor of Array constexpr and you need to actually fill the values of test1 inside calculations, because accessing uninitialized values causes undefined behavior and undefined behavior makes expressions not constant expressions.

    So all in all:

    template<int Size, typename T>
    struct Array{
        T array[Size];
        constexpr Array(const T * a) {
            for(int i = 0; i < Size; i++){
                array[i] = a[i];
            }
        }
    };
    
    template<typename T, int size>
    class Example{
    
    private:
        Array<size, T> _array;
        public:
            constexpr explicit Example(T * arr):_array(arr){};
            constexpr explicit Example(const T * arr):_array(arr){};
    };
    
    template<typename D, int size, typename ...buf, typename T>
    constexpr auto calculations(const T & myObj){
        D test1[2];
        test1[0] = 0;
        test1[1] = 1;
        // calculation fills arr
        return Example<D, size>(test1);
    }
    
    int main(){
        const int size = 2;
        constexpr double test1[size] = {1,2};
        constexpr auto obj1 = Example<double, size>(test1); //compile time
        //obj2 calculations during compile time or run-time?
        constexpr auto obj2 = calculations<double, size>(obj1);
    }
    

    In C++20 there will be an alternative keyword consteval which one can use instead of constexpr on a function to force it to always be evaluated at compile-time. Currently there is no way to do that without making e.g. the destination of the return value a constexpr variable.

    In fact your original code has undefined behavior. Because Array does not have a constexpr constructor, objects of that type can never be constructed in constant expressions. And because Example uses that type, it cannot be used in constant expressions either. This makes it illegal to put constexpr on its constructor, because a function declared constexpr causes undefined behavior if there isn't at least one valid set of template arguments and function arguments that would produce a constant expression. (The same then applies to calculations as well, because it uses Example.

    So you must put constexpr on the constructor of Array in any case if your program is supposed to be well-formed.

    Whether variables created inside the constant expression (e.g. inside calculations) are const does not matter at all.