Skip to content

[BUG] Variable with placeholder type can't share name with member call in initializer #550

Closed

Description

@JohelEGP

Title: Variable with placeholder type can't share name with member call in initializer.

Description:

Something in the UFCS machinery breaks this use case.

My actual use case looks more like this (https://cpp2.godbolt.org/z/sGK36zbe1):

  f := :() = {
    v&$*.f();
  };

It's a function expression that captures.
Using std::function is undesirable,
and currently impossible given #343.

Minimal reproducer (https://cpp2.godbolt.org/z/r7Ef84MKs):

t: type = {
  f: (this) -> i32 = 0;
}
main: () = {
  f := t().f();
}
Commands:
cppfront main.cpp2
clang++17 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -I . main.cpp

Expected result: A working program.

Actual result and error:

Cpp2 lowered to Cpp1:
//=== Cpp2 type declarations ====================================================


#include "cpp2util.h"

class t;
  

//=== Cpp2 type definitions and function declarations ===========================

class t {
  public: [[nodiscard]] auto f() const -> cpp2::i32;

  public: t() = default;
  public: t(t const&) = delete; /* No 'that' constructor, suppress copy */
  public: auto operator=(t const&) -> void = delete;
};
auto main() -> int;
  

//=== Cpp2 function definitions =================================================


  [[nodiscard]] auto t::f() const -> cpp2::i32 { return 0;  }

auto main() -> int{
  auto f {CPP2_UFCS_0(f, t())}; 
}
Output:
main.cpp2:5:23: error: variable 'f' declared with deduced type 'auto' cannot appear in its own initializer
    5 |   auto f {CPP2_UFCS_0(f, t())}; 
      |                       ^
1 error generated.

Activity

  1. JohelEGP commented on Aug 10, 2023

    @JohelEGP
    ContributorAuthor

    All compilers reject https://compiler-explorer.com/z/4f3d818hs:

    class t { int f(); };
    int main() {
      auto f = [](auto obj) {
        if constexpr (requires { obj.f(); }) return obj.f();
        else return f(obj);
      }(t());
    }

    (Opened https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110978 for GCC's ICE.)
    Maybe overload_linearly would work.
    No, it doesn't: https://compiler-explorer.com/z/9vnKvveK5.

    #include <boost/hana/functional/overload_linearly.hpp>
    class t { int f(); };
    int main() {
      auto f = boost::hana::overload_linearly(
        [](auto obj) requires requires { obj.f(); } { return obj.f(); },
        [](auto obj) requires requires { f(obj); } { return f(obj); }
      )(t());
    }
  2. JohelEGP commented on Aug 29, 2023

    @JohelEGP
    ContributorAuthor

    Rewriting Cpp1 auto f {t().f()};
    to Cpp2 f := t().f();
    can result in ill-formed code.
    You'd either need to change the member's name,
    or come up with a new name for the local variable.

    Maybe there's something from the exploration of #307 I can salvage to solve this issue.
    Nope (#307 (comment)):

    Even then, with the new branch in the macro, a variable named after a member function breaks UFCS

  3. JohelEGP commented on Aug 30, 2023

    @JohelEGP
    ContributorAuthor

    I think the solution is to disable UFCS
    if name lookup finds that the function identifier being called
    is a variable and we are in its initializer.

  4. JohelEGP commented on Oct 29, 2023

    @JohelEGP
    ContributorAuthor

    From #788 (comment):

    #506's resolution for #550 adds quite some complexity to Cppfront.
    The same can be done here to resolve this #788.
    The alternative is to just embrace #550 and #788 as limitations of Cppfront,
    and require users to not name such variables like the function in a UFCS call.

  5. JohelEGP commented on Nov 22, 2023

    @JohelEGP
    ContributorAuthor

    Here is a data point that suggests Cpp1 code equivalent to Cpp2 𝘮𝘦𝘮 := 𝘷𝘢𝘳.𝘮𝘦𝘮(); is prolific.
    There are 12.1 K results precisely for auto size = 𝘷𝘢𝘳.size(); (https://github.com/search?q=lang%3AC%2B%2B+content%3A%2Fauto+size+%3D+%28%3F%3A%5Ba-zA-Z_%5D%2B%29%5C.size%5C%28%5C%29%3B%2F+NOT+is%3Afork&type=code).

  6. JohelEGP commented on Nov 30, 2023

    @JohelEGP
    ContributorAuthor

    There is another case that #506 doesn't yet cover (https://cpp2.godbolt.org/z/hzYxv5rn6):

    type_declaration: @struct type = {
      require: (this) = { }
    }
    require_fn: type == std::function<void(bool, std::string_view)>;
    array2: (inout t: type_declaration) = {
      require: require_fn = :(args...) t&$*.require(args...);
      _ = require;
    }
    main: () = { }
    
    main.cpp2:6:91: error: variable 'require' cannot be implicitly captured in a lambda with no capture-default specified
        6 |   require_fn require {[_0 = (&t)](auto const& ...args) mutable -> auto { return CPP2_UFCS(require, (*cpp2::assert_not_null(_0)), args...);  }};
          |                                                                                           ^
    main.cpp2:6:14: note: 'require' declared here
        6 |   require_fn require {[_0 = (&t)](auto const& ...args) mutable -> auto { return CPP2_UFCS(require, (*cpp2::assert_not_null(_0)), args...);  }};
          |              ^
    

    require has type require_fn, so it isn't that its type is deduced.
    But it still tries to use require without capturing it.
    Name lookup finds an object, so it doesn't try a free function, and the object must be captured.
    If we're in a context that would need to capture the name, we should not use the UFCS macro.
    In the context of a member function, this would happen to work due to #281.


    There is another case that #506 doesn't yet cover (https://cpp2.godbolt.org/z/xj1P9cd66):

    type_declaration: @struct type = {
      require: (this) = { }
    }
    require_fn: type == std::function<void(bool, std::string_view)>;
    array2: (inout t: type_declaration) = {
      require: std::optional = t.require();
      _ = require;
    }
    main: () = { }
    
    main.cpp2:6:38: error: variable 'require' declared with deduced type 'std::optional' cannot appear in its own initializer
        6 |   std::optional require {CPP2_UFCS_0(require, t)};
          |                                      ^
    main.cpp2:6:17: error: no viable constructor or deduction guide for deduction of template arguments of 'optional'
        6 |   std::optional require {CPP2_UFCS_0(require, t)};
          |                 ^
    

    This is the same as #550 (comment).
    Except that we can't easily identify that CTAD is required.
    Maybe a nested requirement such as requires { typename std::optional; } might work to detect it.

  7. JohelEGP commented on Nov 30, 2023

    @JohelEGP
    ContributorAuthor

    require: require_fn = :(args...) t&$*.require(args...);

    For this kind of code, in GitHub,
    there are precisely 7 uses with the identifier size
    that would break if they were rewritten to Cpp2 and compiled with cppfront:
    https://github.com/search?q=lang%3AC%2B%2B+content%3A%2F+size+%3D+%5C%5B.*%5C.size%5C%28%5C%29%3B%2F+NOT+is%3Afork&type=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

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions