0

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);
}
M.Mac
  • 699
  • 3
  • 14
  • As far as I know, you cannot *force* a `constexpr` function to be evaluated at compile time. Deferring to run-time is always a valid option. – Jesper Juhl Nov 09 '19 at 15:34

2 Answers2

3

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.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • I thanks you very much for the detailed explanation. Now i have a bigger picture of the `constexpr` and how i have to use it. It is also very interesting that variables created inside the constant expression doesn't have to be `const`, as long as the objects are in a context that, as you said, requires a compile time calculation. – M.Mac Nov 09 '19 at 16:35
  • 1
    @M.Mac A big point of confusion usually is the difference between `constexpr` and "*constant expression*". These are two different, but related concepts. `constexpr` on a function only means that it *can be sometimes* evaluated at compile time. And once a compile-time evaluation has started, you can do whatever you want in it, with exceptions. See [cppreference](https://en.cppreference.com/w/cpp/language/constant_expression) for a list of things you can *not* do in a constant expression. Even so a constant expression does only *need* to be evaluated at compile-time in a context *requiring* it. – walnut Nov 09 '19 at 16:39
  • so basically, if you obey the rules of a "Core constant expression" by not evaluating any of the given points at cppreference, you can calculate anything. Thank you for the link! – M.Mac Nov 09 '19 at 17:13
1

from Microsoft:

A constexpr function is one whose return value can be computed at compile time when consuming code requires it. Consuming code requires the return value at compile time, for example, to initialize a constexpr variable or provide a non-type template argument. When its arguments are constexpr values, a constexpr function produces a compile-time constant. When called with non-constexpr arguments, or when its value isn't required at compile time, it produces a value at run time like a regular function. (This dual behavior saves you from having to write constexpr and non-constexpr versions of the same function.)

so your calculate function will evaluated compile time if all parameters are constexpr

Ahmed Anter
  • 650
  • 4
  • 13
  • 1
    "*or when its value isn't required at compile time, it produces a value at run time like a regular function*": That is saying that it will *not* be evaluated at compile-time in OP's case, because a compile-time constant is not required to initialize the variable. Even so, I assume that usually MSVC will do the calculation at compile-time if it can. In OP's case the evaluation can *never* be done at compile-time though, because they are not satisfying the `constexpr` requirements. Finally, even all that aside, Microsoft's documentation does not explain behavior of other compilers. – walnut Nov 09 '19 at 16:16
  • he declared obj2 as const and should be initialized by the return of calculate function this requires its value to be present at compile time especially it is declared as constexpr so the compiler will try to eval it compile time and will find a value.thats what I think. – Ahmed Anter Nov 09 '19 at 16:32
  • 1
    No a `const` variable does not require its initializer value to be known at compile time. A `constexpr` variable does, though. See my answer. As I further explained in my answer OP's code can never be evaluated as constant expression (as-if rule aside). And even if, in this case it is the compiler's decision whether to evaluate at compile- or runtime. Nothing is forcing the former. – walnut Nov 09 '19 at 16:35