Question

variable conditionally declared constexpr according to its initialization expression

I'd like to be able to conditionally declare a variable constexpr, according to properties of its initializer.

My actual use case is a bit complicated but it can be summarized to:

#include <cmath>
#include <type_traits>

template<typename T>
constexpr double create_val(T x)
{
    // some code that can be constant evaluated or not, according to T
}

int main() {

    constexpr_if_possible auto v = create_val(4);

    // w can only be constexpr under certain conditions
    // otherwise it should be at least const
    constexpr_if_possible auto w = create_val(4.0);

    return static_cast<int>(v*w);
}

where constexpr_if_possible would actually mean constexpr when the expression can be constant evaluated, and be const otherwise.

Here is an example to illustrate this:

#include <cmath>
#include <type_traits>

template<typename T>
constexpr double create_val(T x)
{
    static_assert(std::is_arithmetic_v<T>);
    if constexpr (std::is_integral_v<T>)
    {
        // some leggit code in constant expression
        // only an example
        return static_cast<double>(x*x);
    }
    else
    {
        // some not usable code in constant expression
        // only an example
        return std::sqrt(x);
    }
}

int main() {

    // constexpr_if_possible is equivalent to constexpr here
    constexpr auto v = create_val(4);

    // conditions are not met, constexpr_if_possible is equivalent to const
    const auto w = create_val(4.0);

    return static_cast<int>(v*w);
}

clang and msvc would rightfully reject this code with constexpr w due to the use of the non-constexpr function std::sqrt. (gcc accepts it and optimizes away the computation, probably because it already declares std::sqrt as constexpr).
Live

According to the template argument of create_val, the initialized variable can or cannot be constexpr.

If I were in a template function I could use if constexpr but, perhaps, at the price of code duplication.

Obviously, I could also read all the codes to determine "manually" if constant evaluation is possible but, as seen in my example, when I'll change language standard, I'll have to parse again all my code to see if situation has changed.

Is it possible (and how) to declare v, w conditionally constexpr with respect to their initializer? In pseudo-totally-incorrect code:

constexpr_if_possible auto w = create_val(...);

(thanks to @Jarod42 for having help me to formulate constexpr_if_possible)

I've got the impression that the language syntax cannot allow for this but would there be some workaround?

NB In real use case, my variable type can be anything (fundamental types as well as classes).

 3  90  3
1 Jan 1970

Solution

 3

You want a "constexpr_if_possible".

It is what const does for integral types only (inherited from C++98, when constexpr didn't exist). i.e const int x = 42; is equivalent to constexpr int x = 42; whereas const int y = read_int_from(std::cin); is not constexpr.

const might be good enough for non-integral types, compiler would probably optimize it.
For those types, they couldn't be used as NTTP, but if it is required, then you know you have to use constexpr instead.

C++23 reduces restriction about constexpr functions to allow to use functions which might be marked as constexpr in future (standard), but there are nothing to declare variable as constexpr_if_possible.

2024-07-03
Jarod42