cppfront icon indicating copy to clipboard operation
cppfront copied to clipboard

[BUG] UFCS with variable template in scope fails

Open JohelEGP opened this issue 2 years ago • 5 comments
trafficstars

Title: UFCS with variable template in scope fails.

Description:

std::vector<int>().empty() will fail given using std::views::empty;. That should end up calling the member empty of std::vector. But the UFCS macro fails on the free function branch.

All compilers fail: https://compiler-explorer.com/z/bWejqfodq. With #506, MSVC starts accepting: https://compiler-explorer.com/z/TYaMW7av6.

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

#include <ranges>
#include <vector>
using std::views::empty;
auto _ = std::vector<int>().empty();
main: () = {
  _ = std::vector<int>().empty();
}
Commands:
cppfront main.cpp2
clang++18 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -I . main.cpp

Expected result: A well-formed program that calls the member empty of std::vector.

Actual result and error:

Cpp2 lowered to Cpp1:


//=== Cpp2 type declarations ====================================================


#include "cpp2util.h"



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

#include <ranges>
#include <vector>
using std::views::empty;
auto _ = std::vector<int>().empty();
auto main() -> int;
  

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


auto main() -> int{
  static_cast<void>(CPP2_UFCS_0(empty, std::vector<int>()));
}
Output:
main.cpp2:6:33: error: use of variable template 'empty' requires template arguments
    6 |   static_cast<void>(CPP2_UFCS_0(empty, std::vector<int>()));
      |                                 ^
/opt/compiler-explorer/clang-trunk-20231104/bin/../include/c++/v1/__ranges/empty_view.h:45:36: note: template is declared here
   44 |   template <class _Tp>
      |   ~~~~~~~~~~~~~~~~~~~~
   45 |   inline constexpr empty_view<_Tp> empty{};
      |                                    ^
1 error generated.

JohelEGP avatar Nov 04 '23 23:11 JohelEGP

I noticed the same failure if you attempt to call std::vector<int>().std::vector<int>::empty() (https://cpp2.godbolt.org/z/c5veMnjMe). Branch main: https://compiler-explorer.com/z/9oa1Ga3Tx (but see #555). #506: https://compiler-explorer.com/z/qvK7E4Kj8. Except that Clang's error message requests for disambiguating template.


This issue came up while implementing @rule_of_zero at #797. Fortunately, I could fix it with a local using std::ranges::empty; // Pending #506. (std::views::empty actually came from using namespace std::views;). So it doesn't directly use std::vector::empty. I was fortunate enough to have such a fix-hack at hand.

JohelEGP avatar Nov 05 '23 02:11 JohelEGP

I think this one really is IFNDR. Reading https://eel.is/c++draft/algorithms.requirements#2 reminded me that the else branch, although never taken for std::vector, will always be ill-formed for all invocations (because std::views::empty needs a template argument).

JohelEGP avatar Nov 07 '23 13:11 JohelEGP

IIRC from my analysis at #307, it's that this can't be solved without Cpp1 reflection.

JohelEGP avatar Nov 07 '23 15:11 JohelEGP

The same seems to happen for operator(): https://compiler-explorer.com/z/65Y5rh7P5.

JohelEGP avatar Nov 09 '23 17:11 JohelEGP

The one case we can handle is when source local name lookup results in a variable. If it isn't templated, func<targs>(obj) can't be valid, so the only option is the member call. See https://cpp2.godbolt.org/z/T6sdezYKh:

t: @struct type = {
  f: <T> (this) = { }
  g: <T> (this) = { }
}
main: () = {
  t().f<int>(); // OK.

  g := 0;
  // error: 'g' does not name a template but is followed by template arguments
  t().g<int>();
  _ = g;
}
auto main() -> int{
  CPP2_UFCS_TEMPLATE(f<int>)(t());// OK.
  auto g {0}; 
  CPP2_UFCS_TEMPLATE(g<int>)(t());// S
  static_cast<void>(std::move(g));
}
main.cpp2:10:22: error: 'g' does not name a template but is followed by template arguments
   10 |   CPP2_UFCS_TEMPLATE(g<int>)(t());
      |                      ^~~~~~
raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:855:84: note: expanded from macro 'CPP2_UFCS_TEMPLATE'
  855 | #define CPP2_UFCS_TEMPLATE(...)                           CPP2_UFCS_(&,(),template,__VA_ARGS__)
      |                                                                                    ^~~~~~~~~~~
raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:845:51: note: expanded from macro 'CPP2_UFCS_'
  845 |   noexcept(CPP2_UFCS_IS_NOTHROW_ARG(QUALID,TEMPKW,__VA_ARGS__)) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) \
      |                                                   ^~~~~~~~~~~
raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:803:90: note: expanded from macro 'CPP2_UFCS_IS_NOTHROW_ARG'
  803 | #define CPP2_UFCS_IS_NOTHROW_ARG(QUALID,TEMPKW,...)   CPP2_UFCS_IS_NOTHROW(QUALID,TEMPKW,__VA_ARGS__)
      |                                                                                          ^~~~~~~~~~~
raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:801:60: note: expanded from macro 'CPP2_UFCS_IS_NOTHROW'
  801 |               requires noexcept(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(std::declval<Obj>(), std::declval<Params>()...)); }
      |                                                            ^~~~~~~~~~~
main.cpp2:8:8: note: non-template declaration found by name lookup
    8 |   auto g {0}; 
      |        ^

JohelEGP avatar Dec 13 '23 13:12 JohelEGP