C++ Logo

std-proposals

Advanced search

Re: [std-proposals] constraint that function can/shall be evaluated at compile time

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Sun, 15 Sep 2024 10:58:59 -0400
On Sun, Sep 15, 2024 at 4:38 AM Robin Savonen Söderholm via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> Hi!
>
> Another thing I found that I'd like in concepts is the notion that e.g. a
> function related to a type can or shall be evaluated at compile time. For
> example, this:
> ```c++
> template <typename T>
> concept has_foo = requires() {
> { T::foo() /*consteval*/ } -> is_foo_like;
> };
> ```
> This is because I may want to use the result of `foo` to distinguish what
> kind of reflections I should be allowed to use on `T`, and what should be
> considered a compile error.
>

Unfortunately, "constexpr-friendliness" is a dynamic property, based on
runtime-ish *values*, not on compile-time-ish *types*.
Consider:
    struct A { static constexpr int foo(int i) { if (i == 42) puts("hi");
return i; } };
    template<class T> concept Fantasy = requires (int i) { A::foo(i)
consteval; }; // fantasy syntax for "A::foo(i) is constexpr-friendly"
Now, is `Fantasy<A>` true or false?
The expression `A::foo(41)` is certainly constexpr-friendly.
But the expression `A::foo(42)` is not constexpr-friendly; it's evaluable
only at runtime.
So what is the constexpr-friendliness of the expression `A::foo(i)` in a
vacuum, without knowing the value of `i`? We don't know.

This is a real problem, but I think it's fundamentally unsolvable given the
tools we have designed for ourselves in C++.
In fact there's an *even harder* problem for Concepts, which goes like this:

    struct B { static constexpr int bar(int i) { return i; } };
    struct C { static consteval int bar(int i) { return i; } };
    template<class T> int runtime(int i) { return T::bar(i); } //
instantiating with B is OK; with C is ill-formed
    template<class T> concept RuntimeFriendly = requires (int i) {
T::bar(i); };
    static_assert(RuntimeFriendly<B>);
    static_assert(not RuntimeFriendly<C>); // we'd like this to succeed,
but compilers reject this today

We need a way to distinguish "concepts [or SFINAE] about runtime" from
"concepts [or SFINAE] about compile-time." Otherwise, the space of "stuff
you can do with a C++ expression" will continue to outpace the space of
"stuff you can test with SFINAE/concepts."

I have the vague sense that someone, like Barry, might be working on the
latter problem. But maybe that's just because I see people (like Barry)
working on fixing up the `consteval` feature and so I blindly assume that
this would be part of that whole package.

A solution to the latter problem is certainly required by one or more
otherwise-nice papers such as
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3295r0.html (Ben
Craig proposes to make std::allocator available on freestanding but as
"consteval only," which means all your concepts/traits are going to give
the wrong answers regarding runtime, unless we can solve the problem)

TLDR, we started with "runtime stuff" and then "compile-time
Concepts/SFINAE that lets us ask questions about runtime stuff."
Then we added "constexpr stuff, i.e. a compile-time runtime"; but we
*failed* to add "compile-time compile-time stuff that lets us ask questions
about the compile-time stuff."

Off the top of my head, I suppose one possible syntax for this would be,
like,
    template<class T> concept constexpr Fantasy = requires (int i = 42) {
A::foo(i); };
    template<class T> concept RuntimeFriendly = requires (int i) T::bar(i);
};
    template<class T> concept consteval DontCareAboutRuntime = requires
(int i = 42) { A::allocate(i); };
where `DontCareAboutRuntime<std::allocator<int>>` might be `true` on
freestanding, even though no runtime function `A::allocate` would exist.
Notice the explicit providing of "default values" for each
requires-expression parameter.

HTH,
Arthur

Received on 2024-09-15 14:59:12