Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[concepts] deferred substitution into requirements of class template members not implemented #44178

Closed
marehr opened this issue Feb 7, 2020 · 45 comments
Labels
bugzilla Issues migrated from bugzilla c++20 clang:frontend Language frontend issues, e.g. anything involving "Sema" concepts C++20 concepts

Comments

@marehr
Copy link

marehr commented Feb 7, 2020

Bugzilla Link 44833
Version trunk
OS Linux
CC @CaseyCarter,@erichkeane,@friedkeenan,@h-2,@JohelEGP,@AlexeyDmitriev,@PiliLatiesa,@zygoloid,@saarraz,@HighCommander4,@jwakely

Extended Description

Sorry for this verbose and hard to read code, but I wanted to make this configurable.

This code basically has multiple code paths. If REQUIRES_BUG is set, the impl function uses requires to infer if char_is_valid_for is defined or not. If not impl uses SFINAE.

#include <utility>

// #define REQUIRES_BUG     1 - enable the requires bug, 0 - disable it
// #define FUNCTION_DEFINED 1 - define function char_is_valid_for, 0 - do not define char_is_valid_for 

template <int I> struct priority_tag : priority_tag<I - 1> {};
template <> struct priority_tag<0> {};

struct alphabet_base {};

#if FUNCTION_DEFINED
using expect_return_type = bool;
bool char_is_valid_for(char, alphabet_base) { return true; };
#else
using expect_return_type = int;
#endif

namespace seqan3::detail::adl_only {

template <typename...> void char_is_valid_for(...) = delete;

template <typename alph_t>
struct char_is_valid_for_fn
{
    using s_alph_t = alph_t;

#if REQUIRES_BUG
    template <typename t>
    static decltype(auto) impl(priority_tag<1>, t v)
        requires requires { (char_is_valid_for(v, s_alph_t{})); }
    { return (char_is_valid_for(v, s_alph_t{})); }
#else
    template <typename t>
    static auto impl(priority_tag<1>, t v)
        -> std::enable_if_t<true, decltype(char_is_valid_for(v, s_alph_t{}))>
    { return (char_is_valid_for(v, s_alph_t{})); }
#endif

    template <typename t, typename = void>
    static decltype(auto) impl(priority_tag<0>, t v) { return 0; }
};
} // namespace seqan3::detail::adl_only

int main() {
  auto a = seqan3::detail::adl_only::char_is_valid_for_fn<alphabet_base>::impl(priority_tag<1>{}, char{});
  static_assert(std::is_same_v<decltype(a), expect_return_type>);
}

As you can see on https://godbolt.org/z/tyeiJ2 when defining clang++ -std=c++2a -DREQUIRES_BUG=1 -DFUNCTION_DEFINED=0 the requires block should silently see that the requires is not valid (because of a deleted function) and skip to the next definition.

Error Message:

<source>:31:15: error: call to deleted function 'char_is_valid_for'
    { return (char_is_valid_for(v, s_alph_t{})); }
              ^~~~~~~~~~~~~~~~~

<source>:45:75: note: in instantiation of function template specialization 'seqan3::detail::adl_only::char_is_valid_for_fn<alphabet_base>::impl<char>' requested here

  auto a = seqan3::detail::adl_only::char_is_valid_for_fn<alphabet_base>::impl(priority_tag<1>{}, char{});
                                                                          ^

<source>:20:29: note: candidate function [with $0 = <>] has been explicitly deleted
template <typename...> void char_is_valid_for(...) = delete;
                            ^

<source>:45:8: error: variable has incomplete type 'void'
  auto a = seqan3::detail::adl_only::char_is_valid_for_fn<alphabet_base>::impl(priority_tag<1>{}, char{});
       ^

2 errors generated.

Interestingly, if you substitute s_alph_t with alph_t; everything works as expected.

@zygoloid
Copy link
Mannequin

zygoloid mannequin commented Feb 7, 2020

The difference between GCC and Clang is due to an open question for C++20 concepts: do we defer substitution into member requires-clauses or not? The current thinking in the committee is that we do not, which is what Clang implements (the older direction was that we would defer such substitution, which is what GCC implements).

You can avoid this problem by using a named concept instead of an inline requires-clause: https://godbolt.org/z/XCjyw_

@zygoloid
Copy link
Mannequin

zygoloid mannequin commented Feb 7, 2020

(Using a named concept allows the code to be written a bit more concisely too: https://godbolt.org/z/QsSnXM)

@marehr
Copy link
Author

marehr commented Feb 18, 2020

Thank you Richard Smith for the quick response.

So if I understand you correctly an inline requires is currently okay, if I don't use anything declared within the context of the class.

That means

Do you know what the voting in the last meeting on this was and what the resolution is?

As a user I think of inline requires (if you don't use them in combination with named concepts) as glorified enable_if's with better diagnostic output. Because subsumption can only take place with named concepts, so I personally would prefer if they behave the same.

This code was reduced and was originally part of a macro that made CPO overloads easier, if I have to use named concepts this would defeat the purpose of that macro.

#define SEQAN3_CPO_IMPL(PRIO, TERM) \
template <typename t, typename ...arg_ts> \
static constexpr decltype(auto) impl(seqan3::detail::priority_tag<PRIO> const &, t && v, [[maybe_unused]] arg_ts && ... args) \
    requires requires (seqan3::detail::priority_tag<PRIO> const &, t && v, arg_ts && ... args) \
    { { TERM }; } \
{ return TERM; }

But maybe we have to find a different solution.

On a side note, none of my/our solutions work with msvc.

@zygoloid
Copy link
Mannequin

zygoloid mannequin commented Feb 19, 2020

Do you know what the voting in the last meeting on this was and what the resolution is?

The rule that we decided on is this:

  • Substitution of template arguments into a class does not substitute into constraints, ever.
  • Constrained friends in classes that mention template arguments from outside the friend are "unique", do not redeclare anything else, and must be definitions. (This avoids the need to substitute into their constraints for mangling and declaration matching.)
  • Declaration matching for out-of-class explicit specializations, instantiations, and so on, of a member of a class template does perform full substitution into the constraints; substitution failure in the immediate context results in the declaration not matching.

This is closer to GCC's behavior than to Clang's, and in particular means your example is valid and this is a Clang bug.

@CaseyCarter
Copy link
Member

+1, stumbled across this implementing std::ranges::view_interface (which is specified to expect "Thou shalt not substitute into requires-clauses before overload resolution!").

@llvmbot
Copy link
Member

llvmbot commented Aug 10, 2020

Is this the same issue that prevents the use of ranges from GCC 10.2.0 libstdc++?

https://godbolt.org/z/f51ses

Which boils down to:

template <typename D>
struct CRTP {
   void call_foo() requires
     requires (D& v) { v.foo();} {
       static_cast<D*>(this)->foo();
   }
};

struct Test: public CRTP<Test> {
   void foo() { }
};

int main()
{
   Test t;
   t.call_foo();
   return 0;
}
<source>:16:7: error: invalid reference to function 'call_foo': constraints not satisfied
   t.call_foo();
     ^
<source>:4:27: note: because 'v.foo()' would be invalid: no member named 'foo' in 'Test'
     requires (D& v) { v.foo();} {
                         ^

https://godbolt.org/z/8jrz9q

@zygoloid
Copy link
Mannequin

zygoloid mannequin commented Oct 11, 2020

*** Bug llvm/llvm-bugzilla-archive#47508 has been marked as a duplicate of this bug. ***

@PiliLatiesa
Copy link

+1

Having to use a named concept or boolean variable is counter-intuitive.

@friedkeenan
Copy link

I believe this is a genuine bug, as the standard seems to expect that the requires clause of a method is only evaluated when the method is used, see std::ranges::view_interface: https://eel.is/c++draft/view.interface

Those requires clauses can't be satisfied with incomplete types.

@erichkeane
Copy link
Collaborator

Is this the same issue that prevents the use of ranges from GCC 10.2.0
libstdc++?

https://godbolt.org/z/f51ses

Which boils down to:

template <typename D>
struct CRTP {
   void call_foo() requires
     requires (D& v) { v.foo();} {
       static_cast<D*>(this)->foo();
   }
};

struct Test: public CRTP<Test> {
   void foo() { }
};

int main()
{
   Test t;
   t.call_foo();
   return 0;
}
<source>:16:7: error: invalid reference to function 'call_foo': constraints
not satisfied
   t.call_foo();
     ^
<source>:4:27: note: because 'v.foo()' would be invalid: no member named
'foo' in 'Test'
     requires (D& v) { v.foo();} {
                         ^

https://godbolt.org/z/8jrz9q

Thanks for the reproducer! I've poked around this for a few days, and this example is pretty clarifying. In this case, the constraint fails because we've been evaluating it immediately upon instantiation of the class (in the definition of 'Test''s inheritence), rather than waiting until it is called.

I see that Richard posted about it here:

Substitution of template arguments into a class does not substitute into constraints, ever.

Which I think is likely the solution to the ranges problem as well! Unfortunately, it is a pretty significant architectural change to make this happen as far as I can tell. I'm still looking into it, as compiling ranges is pretty important for us.

@erichkeane
Copy link
Collaborator

Do you know what the voting in the last meeting on this was and what the resolution is?

The rule that we decided on is this:

  • Substitution of template arguments into a class does not substitute
    into constraints, ever.
  • Constrained friends in classes that mention template arguments from
    outside the friend are "unique", do not redeclare anything else, and must be
    definitions. (This avoids the need to substitute into their constraints for
    mangling and declaration matching.)
  • Declaration matching for out-of-class explicit specializations,
    instantiations, and so on, of a member of a class template does perform
    full substitution into the constraints; substitution failure in the
    immediate context results in the declaration not matching.

This is closer to GCC's behavior than to Clang's, and in particular means
your example is valid and this is a Clang bug.

Hi Richard-
I'm trying to work through this(at least the 1st bullet at least), but am having trouble finding the appropriate place to do the instantiation work for a called function. I was wondering if you had a link to the wording that made that change? Or some suggestion on how to go about doing those checks?

@jwakely
Copy link
Mannequin

jwakely mannequin commented Oct 18, 2021

+1, stumbled across this implementing std::ranges::view_interface (which is
specified to expect "Thou shalt not substitute into requires-clauses before
overload resolution!").

Is this the same issue that prevents the use of ranges from GCC 10.2.0
libstdc++?

Yes: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102807

@AaronBallman
Copy link
Collaborator

AaronBallman commented Oct 18, 2021

*** #49634 has been marked as a duplicate of this bug. ***

@h-2
Copy link
Member

h-2 commented Nov 23, 2021

Is there any progress on this issue? Does it indeed break all usage of views from libstdc++?

@zygoloid
Copy link
Mannequin

zygoloid mannequin commented Nov 27, 2021

mentioned in #46852

@AaronBallman
Copy link
Collaborator

AaronBallman commented Nov 27, 2021

mentioned in #49634

@erichkeane
Copy link
Collaborator

erichkeane commented Nov 27, 2021

mentioned in #50208

@llvmbot llvmbot transferred this issue from llvm/llvm-bugzilla-archive Dec 10, 2021
@erichkeane
Copy link
Collaborator

Is there any progress on this issue? Does it indeed break all usage of views from libstdc++?

Sorry @h-2 that this took a while to respond to. My understanding is yes, this breaks a significant portion of the 'ranges' views (and much of the rest of ranges as well). I looked at it for a while and made only a minimum amount of progress, not enough to consider it 'progress'. I can disable the premature instantiation of the concept, but cannot figure out how to properly re-instantiate it when it needs to be done.

I suspect someone who understands the template instantiation engine better than I (like @zygoloid ) would have to either give some extremely good hints, or just do it themselves :(

@h-2
Copy link
Member

h-2 commented Dec 19, 2021

Thanks for the update! It seems like this is really a showstopper for lots of C++20 goodness, but there is not much I can do to help. If you do get any experimental code, I would of course be willing to test it.

@erichkeane
Copy link
Collaborator

As an update... I'm still working on this as my non-emergency-top-priority type thing. I FEEL like I'm making progress, but I've still got 5 tests that crash.

@erichkeane
Copy link
Collaborator

I FINALLY got a version together that doesn't assert/crash on any of the lit tests, but I'm sure there are at least one issue left: https://reviews.llvm.org/D119544

I currently have 7 lit test failures, and some seem to be that some template arguments are reversed for some reason. I'll need to take a look at those, but this is at least something that could be 'played with'.

sfoster1 added a commit to Opentrons/ot3-firmware that referenced this issue Feb 15, 2022
The tool detection will also be needed on the pipette. In addition to
therefore needing the tool detection code to live in common/ somewhere,
the pipettes also need to interact with the code in a different way -
they don't care what kind of tool they are (or maybe do only as a
debug), just what carrier they're on. In the same way, the head doesn't
really care what carrier it reads, since it knows which pins it's
looking at, just which tool it is. So we can provide separate filtering
options for each and let each use of the code become simpler.

Unfortunately ranges and views just don't work in clang at all because
of llvm/llvm-project#44178 so even though it
all compiles correctly we have to do a pretty ugly hack that removes
functionality when linting. It would be nice to not have this.
sfoster1 added a commit to Opentrons/ot3-firmware that referenced this issue Feb 15, 2022
The tool detection will also be needed on the pipette. In addition to
therefore needing the tool detection code to live in common/ somewhere,
the pipettes also need to interact with the code in a different way -
they don't care what kind of tool they are (or maybe do only as a
debug), just what carrier they're on. In the same way, the head doesn't
really care what carrier it reads, since it knows which pins it's
looking at, just which tool it is. So we can provide separate filtering
options for each and let each use of the code become simpler.

Unfortunately ranges and views just don't work in clang at all because
of llvm/llvm-project#44178 so even though it
all compiles correctly we have to do a pretty ugly hack that removes
functionality when linting. It would be nice to not have this.
sfoster1 added a commit to Opentrons/ot3-firmware that referenced this issue Feb 16, 2022
- refactor(head,common): commonize tool detect

The tool detection will also be needed on the pipette. In addition to
therefore needing the tool detection code to live in common/ somewhere,
the pipettes also need to interact with the code in a different way -
they don't care what kind of tool they are (or maybe do only as a
debug), just what carrier they're on. In the same way, the head doesn't
really care what carrier it reads, since it knows which pins it's
looking at, just which tool it is. So we can provide separate filtering
options for each and let each use of the code become simpler.

Unfortunately ranges and views just don't work in clang at all because
of llvm/llvm-project#44178 so even though it
all compiles correctly we have to do a pretty ugly hack that removes
functionality when linting. It would be nice to not have this.

- refactor(head,common): commonize adc support

There's some ADC stuff that's different between devices (well, a lot of
ADC stuff) but the reading->mv transition isn't, so we might as well
commonize it.

The core element here is an ADC channel, which is templatized with the
conversion variables. That lets it do some nice conversions, and with
some required implementations in the child we can have a nice
generalizable interface.

There isn't an ADC interface in common, because that's going to go a
bunch of different places - the head has one, for instance, that
specifically has gripper, z, and a channels - nothing else needs that.
It relies on the common channel implementation, and the simulation and
test versions override as appropriate.

- feat(pipettes): Detect current mount at boot

Based on the voltage present on the tool sense pin and using the ADC and
mount detection utilities previously added to common/, we can now pretty
easily decide which mount we're on and assign ourselves a canbus id
based on that.

- feat(bootloader): add pipette mount detection

This unfortunately reimplements the mount detection algorithm in C but
it will work just fine (and is checked by unit tests). Bootloaders
should now properly decide which mount they're on.

- fix(bootloader): allow flashing pipettes

This flash command had the wrong openocd config value
@erichkeane
Copy link
Collaborator

FWIW, I was able to get by all of the lit tests today on this patch and tested a handful of ranges examples (including the one from @llvmbot here) https://reviews.llvm.org/D119544.

@EugeneZelenko EugeneZelenko added the clang:frontend Language frontend issues, e.g. anything involving "Sema" label Mar 18, 2022
@erichkeane
Copy link
Collaborator

Patch re-committed as yesterday, but I'm not going to be brave enough to mark this 'closed' for a few more days at minimum :)

@erichkeane
Copy link
Collaborator

Alright, we made it a week now and only have 2 different regressions reported. One was fixed immediately, the other (#57958) is under review. So I think I'm far enough along to have SOME faith this isn't going to be reverted.

Thanks all for the help with testing, and the reports in the first place!

@dimitry-ishenko
Copy link

@erichkeane any chance of this getting back-ported to LLVM/Clang 13?

@erichkeane
Copy link
Collaborator

@erichkeane any chance of this getting back-ported to LLVM/Clang 13?

None whatsoever unfortunately. There have been a number of regressions thanks to this (that I've been working through as they come up), and I'm sure there are likely more. This would be too high risk to backport to any of the 13-15 branches.

So if you want to use this, you'll have to update to Clang 16 when it comes out.

@dimitry-ishenko
Copy link

Thanks @erichkeane. I didn't think so, but I figured it won't hurt to ask. 🤷🏻‍♂️ 😃

@kelbon
Copy link
Contributor

kelbon commented Dec 27, 2022

Alright, we made it a week now and only have 2 different regressions reported. One was fixed immediately, the other (#57958) is under review. So I think I'm far enough along to have SOME faith this isn't going to be reverted.

Thanks all for the help with testing, and the reports in the first place!

It still dont work for = default methods and destructors as i see

https://godbolt.org/z/r77oWoMTr

May be in can be easily fixed (based on previous fix)?

@erichkeane
Copy link
Collaborator

Alright, we made it a week now and only have 2 different regressions reported. One was fixed immediately, the other (#57958) is under review. So I think I'm far enough along to have SOME faith this isn't going to be reverted.
Thanks all for the help with testing, and the reports in the first place!

It still dont work for = default methods and destructors as i see

https://godbolt.org/z/r77oWoMTr

May be in can be easily fixed (based on previous fix)?

Interesting, thanks for pointing this out! @royjacobson was poking around default function constraints at one point as well, so he might have some idea.

@erichkeane
Copy link
Collaborator

erichkeane commented Jan 3, 2023

I see that the code that calls this is part of SetEligibleMethods which @royjacobson wrote a while back, and is intending to implement class.mem.special p6. I don't have a good idea what all is going on there, nor if it was supposed to have been 'eliminated' by that in the first place. I'm hoping Roy can comment.

EDIT:
Looking a little closer, it looks like the calls to SetEligibleMethods is happening while dealing with the 'fields' of the base class, which seems too early? So we're failing to 'add' it to the my_iter type, since it isn't valid when instantiating the base. The only thing I can think of is that we should have done the special-member-func calculation 'as referred to' rather than immediately? Does this make sense Roy?

@royjacobson
Copy link
Contributor

The standard is clear that we can't defer constraints checks for destructors, but P0848 is unclear about when, or if, to perform the constraints checks for the other special member functions.

When I implemented P0848 I decided to preemptively evaluating the constraints when we complete a class, and that it's probably OK. Not doing it would've added a non-trivial effort to support deferred computation of canPassInRegs and class [copy-]triviality, which we currently store as flags in RecordDeclBits. AFAIK pretty much everywhere assumes that those properties are fixed once a class is complete, and changing that seemed to me something worthy to avoid, as long as the standard doesn't explicitly prevents us from doing it.

But maybe this is non compliant and we really should defer the triviality calculations.

@erichkeane
Copy link
Collaborator

Hmm... that seems worthy of a CWG reflector discussion? Mind starting one?

@zygoloid
Copy link
Collaborator

zygoloid commented Jan 3, 2023

I don't think we should, or reasonably can, defer these calculations in general. This seems to be the same kind of situation as in CWG1344, which described another way in which the set of special member functions could change after the class becomes complete.

ericniebler added a commit to NVIDIA/stdexec that referenced this issue Jul 18, 2023
The combination of clang <= 15 and libstdc++'s version of <ranges> runs into llvm/llvm-project#44178.
Willem3141 added a commit to tue-alga/cartocrow that referenced this issue Aug 10, 2023
Using std::ranges::views::reverse breaks when compiling with clang and
libstdc++ as per llvm/llvm-project#44178.
tzlaine added a commit to boostorg/parser that referenced this issue Mar 28, 2024
…on will

not work when concepts are in use until Clang 16 at the earliest.  See:
llvm/llvm-project#44178 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugzilla Issues migrated from bugzilla c++20 clang:frontend Language frontend issues, e.g. anything involving "Sema" concepts C++20 concepts
Projects
Status: No status
Development

No branches or pull requests