Skip to content

[QUESTION] Is &$* a new cpp2 idiom? #247

Closed

Description

@filipsajdak

As there is no possibility of passing a reference to the anonymous function (aka. lambda), you need to capture a variable as a pointer. That makes a new idiom emerge from the code: &$* - capture variable by pointer (and use as a reference).

The following code:

main: () -> int = {
    i := 42;

    l := :() = {
        std::cout << i&$* << std::endl;
    };

    l();
}

Will generate (skipping boilerplate):

[[nodiscard]] auto main() -> int{
    auto i {42}; 

    auto l {[_0 = (&i)](){                                    // capture by pointer
        std::cout << *cpp2::assert_not_null(_0) << std::endl; // dereference in place of use
    }}; 

    std::move(l)();
}

Or maybe I have missed something?


For all that wanders what i&$* means (please remember that cppfront uses postfix operators - https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators). I will add parentheses to explain:

i& - address of i,
(i&)$ - $ captures the value on the left (in that case, it is a pointer to i),
( (i&)$ )* - * dereference the value captured by $ (in that case, it is a pointer to i)

So, effectively i&$* is a reference to i that is captured by a lambda.

Activity

  1. filipsajdak commented on Jan 31, 2023

    @filipsajdak
    ContributorAuthor

    The funny thing is that it looks like grawlix :D

  2. AbhinavK00 commented on Jan 31, 2023

    @AbhinavK00

    That makes me think, as there are no references in cpp2, how will we write functions that return a reference in cpp2? Like the [] operator for containers, or do we just start returning pointers in all cases?
    Val also does not have references but they have projections which work by something called inversion of control, but I don't think those can be implemented at a transpiler level or if herb would even like to implement them.

  3. filipsajdak commented on Jan 31, 2023

    @filipsajdak
    ContributorAuthor

    @AbhinavK00, that is an excellent question. It is already covered by the forward return type passing style.

    fun: (i : int) -> forward int = {
        return i;
    }
    
    fun2: (i) -> forward _ = {
        return i;
    }

    Generates (skipping boilerplate):

    [[nodiscard]] auto fun(cpp2::in<int> i) -> int&{ // this is bug should be const int&
        return i; 
    }
    
    [[nodiscard]] auto fun2(auto const& i) -> decltype(auto){
        return i; 
    }
  4. filipsajdak commented on Jan 31, 2023

    @filipsajdak
    ContributorAuthor

    cppfront syntax concentrates on intentions, not implementation details (reference here is an implementation detail). You can read more here: https://github.com/hsutter/708/blob/main/708.pdf (@hsutter paper about Parameter passing)

  5. AbhinavK00 commented on Jan 31, 2023

    @AbhinavK00

    Thanks for the answer! Now we just need something similar for lambdas lol

  6. gregmarr commented on Jan 31, 2023

    @gregmarr
    Contributor

    So with the current state of cppfront, i means "local variable from inside the function body", i$ means "capture by value" and i&$* means "capture by reference"? I'm not sure I like that.

    I wonder, would it make sense to have i$ capture by reference, and then you can capture that value by adding another capture as i$$? That doesn't quite fit with the rest of the Capture design notes, where a copy is captured "Postconditions: [[post: v.size() == v.size()$ + 1]] -- capture a copy of v.size() on function entry for use later when the whole postcondition is evaluated on function exit".

    "capture" means "take an expression in this context and store a copy of its current value for use later."
    On the other hand, i isn't even in this context yet until you add the $. It could be that the intention was that everything in the outer scope is implicitly in the function scope, with capture by reference, and $ makes it capture a copy of the value, but that seems unsafe compared to current behavior, as it means that you are implicitly capturing things that may go out of scope before they may be used. Maybe there's an intention of having lifetime analysis done for anything captured by reference.

    I guess that's enough speculation for me until @hsutter can clarify his thoughts here.

  7. filipsajdak commented on Jan 31, 2023

    @filipsajdak
    ContributorAuthor

    i&$* means "capture by reference"?

    @gregmarr just to clarify. i&$* does not mean capture by reference. It captures a pointer (auto l {[_0 = (&i)](){ ) and dereferences it. It behaves like a reference in that context (std::cout << *cpp2::assert_not_null(_0) << std::endl;)

  8. gregmarr commented on Jan 31, 2023

    @gregmarr
    Contributor

    @filipsajdak I understand, the effect is the same.

  9. filipsajdak commented on Jan 31, 2023

    @filipsajdak
    ContributorAuthor

    As we discuss references here, I think it is worth mentioning @hsutter sentence he said when I mentioned that std::optional could not hold references:

    That's a feature, not a bug. :) It's deliberate that Cpp2 has no references.

    More here: #106 (comment)

  10. hsutter commented on Mar 1, 2023

    @hsutter
    Owner

    @filipsajdak To answer your original question, yes that's the idiom. I do realize that &$* is an emergent idiom that comes from the design choice of "capture everything by value" + "in-place using $"... and I realize that it can look like line noise. (I had to look up "grawlix" :) ... that it looks like swearing could be a feature, rather than a bug, since it calls attention to what can be a dangerous construct...) I'm sitting back to see how it works out as I/we get experience using it in actual code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions