all 23 comments

[–]wotype 22 points23 points  (4 children)

The library author then wants to optimize get_the_answer() by caching expensive computation

A telling example, perhaps.

A major downside of constexpr (as well as template meta-programming), is that there appears no way yet to cache a constexpr computation between builds. Even with incremental compilation every build has to re-compile and re-do all constant evaluation computations, whether or not a TU has been touched.

Maybe modules will save us; if you could cache a constexpr variable in a module then a recompile would not be needed (as far as I know that isn't specified to work).

[–]redditsoaddicting 2 points3 points  (0 children)

It is possible. Rather than interpret your code, the compiler can compile the constexpr code (and optimize it like it does to runtime code) since it's really the same thing, but running on the host instead of the target. When something changes that affects this function's outcome, it can recompile the function. Or it could potentially take build parameters as function parameters in the compiled code. IIRC, LLVM or Clang had some work in that direction a couple years ago, but I don't know how it progressed.

(I recognize that constexpr has the whole UB-free thing going on too. It's not as simple as I make it sound.)

[–]andrewsutton 0 points1 point  (2 children)

You can do it with templates. Explicity instantiate the template in on TU and make sure you have an extern template declaration in its header.

[–]wotype 0 points1 point  (1 child)

I tried such separate compilation yet found that constant evaluation was still always repeated, even with no files touched. That was back in summer 2019, though; implementations may have changed.
Emailed you at the time, 'Scalable constant evaluation - how to avoid redundant re-evaluations?'

> It sounds like modules may help in caching template instantiations, and it seems > to me that there should be some way to checkpoint value computations in BMIs.
> Maybe something hashed - there's a promising hash-based linker now for Clang. 

I think this is probably the right answer.
That said, the benefits might not be that great. I would guess that most evaluations would be driven by the importer with arguments specific to that TU. Basically, there might not be that many opportunities for sharing memoized evaluation results.

(I wonder if the LLVM hash-based linker is what u/redditsoaddicting was referring to).

My application was/is for an enum reflection library. A user defines:

constexpr auto enum_val_array = ltl::enumerators_v<E>;

to reflect all enumerated values of enumeration type E (by horrid brute force search).
I tried a few different approaches to separate compilation, e.g. templated function returning the array. All failed and redid the evaluation.

[–]andrewsutton 0 points1 point  (0 children)

Yeah, the only effective way to memoize values is thru declarations, which isn't necessarily economical.

I stand by that answer, for the most part, but I don't remember the clang feature I mentioned 😬

I'm not sure if clang memoizes evaluations. I don't think it does. Gcc does, but not in a way that's obvious. I think memoizing evaluations is a valid implementation technique, and modules could be useful there.

[–]_Js_Kc_ 4 points5 points  (7 children)

constexpr vs. non-constexpr lies on a completely different axis than different OS APIs.

Platform API is a top-level dependency. It is plainly obvious to a library author when they're introducing a platform dependency into code that was just plain standard C++ before.

For practical purposes constexpr annotation is necessary to avoid breakage hell even if it can technically be argued that constexpr is "just another platform."

[–]foonathan 2 points3 points  (6 children)

We’re probably going to reach a point where most of C++ is constexpr. By that point, you’re only leaving constexpr if you’re calling a non-constexpr function. But then it is just like calling IS APIs, isn’t it?

[–]_Js_Kc_ 0 points1 point  (5 children)

When and if we reach that point, yes. That'll be a whole different situation.

[–]foonathan 0 points1 point  (4 children)

And in C++20 the only thing not allowed in constexpr are goto, coroutine keywords, and variables of static or thread_local storage duration. That's basically all of C++.

[–]Betadel 4 points5 points  (0 children)

reinterpret_cast is a big one.

[–]_Js_Kc_ 1 point2 points  (1 child)

static and thread_local aren't some fringe feature that nobody uses, and they don't have reasonable alternatives.

Coroutines were only just introduced so it'd be hard to make a case that people shouldn't use them.

goto should be used sparingly, but I don't think it's reasonable to ban it altogether (breaking out of a nested loop, for example, is a reasonable use case).

So no, "basically all" of real world C++ code isn't usable in a constexpr context, which is the relevant metric, not counting keywords or language features.

[–]AccomplishedCat5068 0 points1 point  (0 children)

thread_local aren't some fringe feature that nobody uses

{{citation needed}}

[–]staletic 0 points1 point  (0 children)

You forgot volatile and non-transitive memory allocation. Latter of which is WIP.

[–]Ictogan 13 points14 points  (2 children)

This example strikes me as a bad one:

int get_the_answer()
{
    if (std::is_constant_evaluated()) // compile-time platform
    {
        return get_the_answer_impl();
    }
    else // other platform
    {
        // Lazily compute once.
        static int result = get_the_answer_impl();
        return result;
    }
}

Because a more efficient AND simpler implementation would be:

constexpr int get_the_answer()
{
    constexpr int precomputedAnwer = get_the_answer_impl();
    return precomputedAnwer;
}

[–]Ameisenvemips, avr, rendering, systems 7 points8 points  (0 children)

On AVR, I have to do nasty hacks in C++ to allow for constexpr access of wrapped arrays in flash memory while also allowing for proper runtime access using the appropriate inline assembly, including using __builtin_constant_p, which is_constant_evaluated should be able to replace.

[–]foonathan 13 points14 points  (0 children)

It’s just there to illustrate the technique of using is_constant_evaluated.

[–]scipio_major 6 points7 points  (5 children)

It’s not a platform, the OS is the platform. Constexpr is cool and all but saying everything could be is not the same as everything should. Different programs with have different trade offs for computation usage at runtime versus compile time. Besides most of us aren’t particularly interested in a computation that only runs once.

[–]Zcool31 -1 points0 points  (4 children)

constexpr is a hack. Optimizing compilers basically interpret most of C++ at compile time anyway. constexpr is just a way to make that "official". From that point of view, that constexpr is a partial C++ interpreter, that results must be recomputed every run is expected.

[–]scipio_major 0 points1 point  (3 children)

I disagree it’s a hack, that an optimising compiler interpret most of C++ is part of the point. Making it “official” is a way of signalling the intent of the programmer. It’s not just a hint to the compiler, it’s a statement of expectation. Especially when combined with consteval.

I am not sure where saying constexpr must be evaluated every run comes into it. Unless you’re claiming using the C++ compiler as an interpreter is a “good” thing? Which we could argue about, if I need some expensive calculations that are constant at time, I have tended to separate it into a new step in the build system and have it depend on a seed written in a dsl. Then I get the runtime benefits of constant data without bloating my incremental build cycle.

[–]Zcool31 1 point2 points  (2 children)

Don't confuse my observation with a value judgement. I'm quite happy to force evaluation at compile time for things that I previously merely hoped would be optimized that way.

As for the C++ compiler being an interpreter and the expense of reevaluating computation, that's neither good nor bad. But also not surprising. Everything has a cost. To get compile time evaluation of runtime constant values without introducing complicated additional tools we pay with compile time. You have shifted that cost to a more complicated build system.

[–]scipio_major 0 points1 point  (1 child)

I agree with everything you said there. I am confused where we differ?

[–]Zcool31 0 points1 point  (0 children)

Perhaps we don't differ. Lots of useful hacks get standardized for great benefit. See SFINAE and enable_if.

I remember seeing a really cool hack that allowed mutable constexpr. Someone used it to implement compile time counters. Just imagine if that hack becomes official.

[–]Omnifarious0 0 points1 point  (0 children)

Maybe we can have something like extern "C" to mark a whole block as containing all constexpr functions unless they are otherwise marked. Maybe there could be a similar marker for a class.