C++ Logo

std-proposals

Advanced search

[std-proposals] Fwd: A new kind variable for immediate functions

From: Torben Thaysen <thaysentorben_at_[hidden]>
Date: Sat, 5 Mar 2022 09:00:09 +0100
---------- Forwarded message ---------
From: Torben Thaysen <thaysentorben_at_[hidden]>
Date: Fri, Mar 4, 2022 at 10:47 PM
Subject: Re: [std-proposals] A new kind variable for immediate functions
To: <marcinjaczewski86_at_[hidden]>


I think I see what you mean now. In your version we have

void function() {
    consteval std::string const_string("Test 1");
    std::string normal_string("Test 2");

    constexpr auto c1 = const_string.size(); // valid
    auto c2 = const_string.size(); // invalid,
const_string can't be used outside a constant expression
    constexpr auto c3 = normal_string.size(); // invalid, normal_string
can't be used in a constant expression
    auto c4 = normal_string.size(); // valid
}

so the situations where "const_string" and "normal_string" can be used are
in fact completely orthogonal.
What I had in mind was:

consteval void function() {
    consteval std::string const_string("Test 1");
    std::string normal_string("Test 2");

    constexpr auto c1 = const_string.size(); // valid
    auto c2 = const_string.size(); // valid
    constexpr auto c3 = normal_string.size(); // invalid, normal_string
can't be used in a constant expression
    auto c4 = normal_string.size(); // valid
}

Where the second case can also be allowed since we are in an immediate
function and we can use "const_string"
without having to leak it to runtime (because the function can't be called
at runtime).

I think you can and maybe should have both behaviours at the same time:

A consteval function is a function that has to be executed at compile time.
Which means that in regular functions it must
produce a constant expression. But inside of a consteval function you are
allowed to call other consteval functions, without
producing a constant expression since you are already guaranteed to be "at
compile time".

Similarly a "consteval" variable would have to be used at compile time, so
it has to produce a constant expression inside
regular functions but doesn't have to in consteval functions.

In fact this would mean that the example of "auto c2 =
const_string.size();" would be valid even inside a regular function
since "const_string.size()" still produces a constant expression. This
essentially analogous to

consteval int get_value() { return 6; }
int main() {
     int i = get_value();
}

which is perfectly valid. But things like

void use_string(const std::string&); // not constexpr!
int main() {
    consteval std::string str("Test");
    use_string(str);
}

aren't allowed since this really would require leaking the string to
runtime code.

Marcin Jaczewski <marcinjaczewski86_at_[hidden]>:

> pt., 4 mar 2022 o 16:03 Torben Thaysen <thaysentorben_at_[hidden]>
> napisał(a):
> >>
> >> This means we can't use `std::string` as it would "leak" memory from
> >> compile time to run time and mixup life times of compilation and
> >> runtime.
> >
> > That is why I made the restriction to consteval functions. There
> shouldn't be any way to leak the address of a local variable inside a
> consteval function to
> > runtime code. The same is true for any intermediate object created
> during the evaluation of a constant expression. Otherwise we couldn't even
> use
> > vector/string as regular variables in constant expressions. For example
> >
> > consteval auto foo() {
> > std::string s("Test");
> > return s.size();
> > }
> >
> > is clearly valid and there better be no way to leak the memory allocated
> by "s" to runtime. And that doesn't change when we make "s" a "consteval"
> variable.
> >
>
> You miss my point, we have effectively two types of `std::string` now,
> one allocated by compiler and another by runtime.
> We can't mix them and even refer to them in the same context at once.
> I hope that my approach simply remedy problem closer to root cause and
> allow easier to transfer data between this two "scopes" aka compile
> time and runtime.
>
>
> > Marcin Jaczewski <marcinjaczewski86_at_[hidden]>:
> >>
> >> pt., 4 mar 2022 o 14:53 Torben Thaysen <thaysentorben_at_[hidden]>
> napisał(a):
> >> >>
> >> >> I think this should be fixed a bit diffrent way. Current `constexpr`
> >> >> variables can materialize in result code and this is problem if we
> use
> >> >> memory allocation.
> >> >
> >> > I am not sure what you mean. The way I understand it, whether you
> find some trace of constexpr variables in the final binary/assembly
> >> > (or whether they are "materialized") is decided by the compiler the
> same way as for any other (inline) variable. Since what I am proposing is
> >> > a local variable inside of immediate functions, the only place they
> could "materialize" is into the body of that function, which by definition
> doesn't
> >> > exist at runtime.
> >> > When you try something like
> >> >
> >> > consteval const int* leak_ptr() {
> >> > consteval int i = 1;
> >> > return &i;
> >> > }
> >> > int main() {
> >> > constexpr auto ptr = leak_ptr();
> >> > }
> >> >
> >> > the local variable won't "materialize", instead you would get a
> compiler error since a pointer to an object of automatic storage duration
> is not
> >> > allowed in a constant expression.
> >> >
> >> I mean case like:
> >> ```
> >> int main()
> >> {
> >> constexpr auto i = 5;
> >>
> >> return (unsigned long int)&i;
> >> }
> >> ```
> >> https://godbolt.org/z/Wx1v5KWjo
> >> We have a real variable on stack with address.
> >> This means we can't use `std::string` as it would "leak" memory from
> >> compile time to run time and mixup life times of compilation and
> >> runtime.
> >> Most of the limitations of `constexpr` is because of this case.
> >>
> >>
> >> >> One way I could see to fix it make option for "pure" `constexpr
> >> >> variables that only live during compilation and are not accessible in
> >> >> anyway by run time.
> >> >
> >> > This sounds quite similar to what I proposed but with the requirement
> to be in an immediate function removed. This might have the advantage
> >> > that it could save you from having to define an extra function.
> However consider that with your version you are restricted to the
> initializer of result
> >> > variable, which means that when the constructor you mentioned doesn't
> exist or you want to do something more complicated the just copy some data,
> >> > you would have to write a helper function anyways.
> >> >
> >>
> >> Yes, but the difference is I make a clear distinction between what
> >> lives at compile time and what lives at runtime. as now `constexpr`
> >> variables can migrate between.
> >>
> >> > Marcin Jaczewski <marcinjaczewski86_at_[hidden]>:
> >> >>
> >> >> pt., 4 mar 2022 o 12:54 Torben Thaysen via Std-Proposals
> >> >> <std-proposals_at_[hidden]> napisał(a):
> >> >> >
> >> >> > Currently when computing things at compile time one sometimes
> faces a problem when trying to output the result to a constexpr variable.
> >> >> > This happens when the result is data structure without a fixed
> size that utilizes dynamic memory allocation, which in itself is not a
> >> >> > constant expression (for example a vector). Today the result can
> be converted to a more appropriate fixed sized structure like this:
> >> >> >
> >> >> > constexpr std::vector<int> compute_vector();
> >> >> > constexpr std::size_t extract_size() { return
> compute_vector().size(); }
> >> >> > constexpr auto as_array() {
> >> >> > auto vec = compute_vector();
> >> >> > std::array<int, extract_size()> arr;
> >> >> > std::copy(vec.begin(), vec.end(), arr.begin());
> >> >> > return arr;
> >> >> > }
> >> >> >
> >> >> > But this somewhat awkwardly involves doing the computation twice.
> The only way around this is to copy the data to a large intermediate array
> >> >> > then copy it again into an array of the right size. Neither of
> these solutions is really ideal and that is what this proposal aims to
> solve.
> >> >> >
> >> >> > For this I want to introduce a new kind of variable that can be
> declared inside an immediate function. With this the above example could
> look like:
> >> >> >
> >> >> > constexpr std::vector<int> compute_vector();
> >> >> > consteval auto as_array() {
> >> >> > consteval auto vec = compute_vector(); // example syntax
> >> >> > std::array<int, vec.size()> arr;
> >> >> > std::copy(vec.begin(), vec.end(), arr.begin());
> >> >> > return arr;
> >> >> > }
> >> >> >
> >> >> > The requirements for this type of variable should conceptually be
> similar to those for constexpr variables (see 9.2.6.10 and 7.7.11) except
> that:
> >> >> >
> >> >> > Targets of pointers and references don't have to have static
> storage duration. In particular pointers to dynamically allocated memory
> are allowed.
> >> >> > Memory allocations performed in the initialization can (and must)
> be deallocated in the destructor.
> >> >> > And (i think) pointers to immediate functions can also be allowed,
> since this type of variable only exists inside immediate functions.
> Although this
> >> >> > isn't required for the aim of this proposal.
> >> >> >
> >> >> > To my reading this lifts all the requirements of 7.7.11 so that
> any core constant expression with the modified allocation requirement would
> be allowed.
> >> >> > Like with constexpr variables these new variables should also be
> implied to be const. And I propose for consistency they should be allowed
> to be
> >> >> > declared in any immediate function context as by 7.7.13 (i.e. also
> inside a if consteval statement).
> >> >> >
> >> >> > Looking forward to your feedback
> >> >> > Torben
> >> >> >
> >> >> > --
> >> >> > Std-Proposals mailing list
> >> >> > Std-Proposals_at_[hidden]
> >> >> > https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
> >> >>
> >> >> I think this should be fixed a bit diffrent way. Current `constexpr`
> >> >> variables can materialize in result code and this is problem if we
> use
> >> >> memory allocation.
> >> >> One way I could see to fix it make option for "pure" `constexpr
> >> >> variables that only live during compilation and are not accessible in
> >> >> anyway by run time.
> >> >> This way we could make:
> >> >> ```
> >> >> int main() //can be used in any function!
> >> >> {
> >> >> consteval auto s1 = std::string("Test"); //variable and memory
> >> >> allocation live only during "compilation" of `main`, we could
> consider
> >> >> this a new `static` in some way.
> >> >> static constexpr std::array<int, s1.size()> a1 = { s1.begin(),
> >> >> s1.end() }; //assume that array have constructor that can copy from
> >> >> range
> >> >>
> >> >> auto s2 = std::string(s1); //Error! `s1` is not accessible here!
> >> >> auto s3 = std::string(a1.begin(), a1.end()); //normal string that
> >> >> copy data from static memory
> >> >> }
> >> >> ```
>

Received on 2022-03-05 08:00:22