This is an archive of the discontinued LLVM Phabricator instance.

[ADT] Fix definition of `adl_begin`/`adl_end` and `Iter`/`ValueOfRange`
ClosedPublic

Authored by kuhar on Feb 22 2023, 11:25 AM.

Details

Summary
  • Make IterOfRange and ValueOfRange work with types that require custom begin/end functions.
  • Allow for adl_begin/adl_end to be used in constant-evaluated contexts.
  • Use SFINAE-friendly trailing return type deductions adl_begin/adl_end so that they are useable in template argument deduction.
  • Add missing documentation comments.

This is required for future work in https://reviews.llvm.org/D144503.

Diff Detail

Event Timeline

kuhar created this revision.Feb 22 2023, 11:25 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 22 2023, 11:25 AM
Herald added a subscriber: hanchung. · View Herald Transcript
kuhar requested review of this revision.Feb 22 2023, 11:25 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 22 2023, 11:25 AM
kuhar updated this revision to Diff 499600.Feb 22 2023, 11:26 AM

Drop unnecessary include

Fix adl_begin/adl_end so that they work with incomplete types. These require trailing return types, per. 7.5.2.: > In a trailing-return-type, the class being defined is not required to be complete for purposes of class member access. Class members declared later are not visible.

Not sure I follow here - could you describe this in more detail, why adding a trailing return type helps? What happens if the trailing return type isn't used here?

kuhar added a comment.EditedFeb 22 2023, 1:08 PM

Fix adl_begin/adl_end so that they work with incomplete types. These require trailing return types, per. 7.5.2.: > In a trailing-return-type, the class being defined is not required to be complete for purposes of class member access. Class members declared later are not visible.

Not sure I follow here - could you describe this in more detail, why adding a trailing return type helps? What happens if the trailing return type isn't used here?

If you return just auto, it fails to compile code like:

template <typename R, typename T = ValueOfRange<R>>
void foo(R&&) { ...

if R is an incomplete type and it cannot lookup the definition of .begin() inside std::begin.

We rely on this in a few places, e.g., llvm::interleave or mlir::Diagnostic::operator<<:

class Operation;
...

/// Stream in a range.
template <typename T, typename ValueT = llvm::detail::ValueOfRange<T>>
std::enable_if_t<!std::is_constructible<DiagnosticArgument, T>::value,
                 Diagnostic &>
operator<<(T &&range) {
  return appendRange(range);
}

Error:

In file included from /usr/local/google/home/kubak/llvm/llvm-project/mlir/lib/IR/MLIRContext.cpp:9:                                                                  
In file included from /usr/local/google/home/kubak/llvm/llvm-project/mlir/include/mlir/IR/MLIRContext.h:13:                                                          
In file included from /usr/local/google/home/kubak/llvm/llvm-project/mlir/include/mlir/Support/TypeID.h:20:                                                          
/usr/local/google/home/kubak/llvm/llvm-project/llvm/include/llvm/ADT/STLExtras.h:67:10: error: no matching function for call to 'begin'                              
  return begin(container);                                                                                                                                      
         ^~~~~~~                                                                                                                                                  
/usr/local/google/home/kubak/llvm/llvm-project/llvm/include/llvm/ADT/STLExtras.h:89:39: note: in instantiation of function template specialization 'llvm::adl_begin<m
lir::Operation &>' requested here                                                                                                                                    
    std::remove_reference_t<decltype(*adl_begin(std::declval<RangeT &>()))>;                                                                                         
                                      ^                                                                                                                              
/usr/local/google/home/kubak/llvm/llvm-project/mlir/include/mlir/IR/Diagnostics.h:209:57: note: in instantiation of template type alias 'ValueOfRange' requested here
  template <typename T, typename ValueT = llvm::detail::ValueOfRange<T>>                                                                                             
                                                        ^                                                                                                            
/usr/local/google/home/kubak/llvm/llvm-project/mlir/include/mlir/IR/Diagnostics.h:212:3: note: in instantiation of default argument for 'operator<<<Operation &>' req
uired here                                                                                                                                                           
  operator<<(T &&range) {                                                                                                                                            
  ^~~~~~~~~~~~~~~~~~~~~                                                                                                                                              
/usr/local/google/home/kubak/llvm/llvm-project/mlir/include/mlir/IR/Diagnostics.h:201:56: note: while substituting deduced template arguments into function template 
'operator<<' [with T = Operation &, ValueT = (no value)]                                                                                                             
  Diagnostic &operator<<(Operation *op) { return *this << *op; }                                                                                                     
                                                       ^                                                                                                             
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/initializer_list:90:5: note: candidate template ignored: could not match 'initializer_list<_Tp>' a
gainst 'mlir::Operation'                                                                                                                                             
    begin(initializer_list<_Tp> __ils) noexcept                                                                                                                      
    ^                                                                                                                                                                
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/range_access.h:52:5: note: candidate template ignored: substitution failure [with _Container 
= mlir::Operation]: member access into incomplete type 'mlir::Operation'                                                                                             
    begin(_Container& __cont) -> decltype(__cont.begin())                                                                                                            
    ^                                           ~                                                                                                                    
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/range_access.h:63:5: note: candidate template ignored: substitution failure [with _Container 
= mlir::Operation]: member access into incomplete type 'const mlir::Operation'                                                                                       
    begin(const _Container& __cont) -> decltype(__cont.begin())                                                                                                      
    ^                                                 ~                                                                                                              
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/range_access.h:95:5: note: candidate template ignored: could not match '_Tp[_Nm]' against 'ml
ir::Operation'                                                                                                                                                       
    begin(_Tp (&__arr)[_Nm]) noexcept                                                                                                                                
    ^                                                                                                                                                                
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/range_access.h:113:31: note: candidate template ignored: could not match 'valarray<_Tp>' agai
nst 'mlir::Operation'                                                                                                                                                
  template<typename _Tp> _Tp* begin(valarray<_Tp>&) noexcept;                                                                                                        
                              ^                                                                                                                                      
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/range_access.h:114:37: note: candidate template ignored: could not match 'valarray<_Tp>' agai
nst 'mlir::Operation'                                                                                                                                                
  template<typename _Tp> const _Tp* begin(const valarray<_Tp>&) noexcept;

I'm not sure how to describe this best.

kuhar added a comment.Feb 22 2023, 2:53 PM

Edit: I think this boils down to trailing return types participating in SFINE and not wanting to emit hard errors when none of the available begin functions matches when adl_begin is used within a template argument.

kuhar edited the summary of this revision. (Show Details)Feb 22 2023, 5:29 PM
kuhar updated this revision to Diff 499684.Feb 22 2023, 5:32 PM

Add missing include

zero9178 accepted this revision.EditedFeb 27 2023, 4:02 AM

Edit: I think this boils down to trailing return types participating in SFINE and not wanting to emit hard errors when none of the available begin functions matches when adl_begin is used within a template argument.

I can see the issue as well. During overload resolution, instantiations of ValueOfRange would otherwise immediately error, instead of being a substitution failure. This is best seen in llvm::interleave:
Instantiating it with a container leads to it calling interleave(c.begin(), c.end(), ...) aka with iterators. This once again tries to find the right interleave overload and when considering:

template <typename Container, typename UnaryFunctor, typename StreamT,
          typename T = detail::ValueOfRange<Container>>
inline void interleave(const Container &c, StreamT &os, UnaryFunctor each_fn,
                       const StringRef &separator)

it actually errors out during substitution of ValueOfRange instead of being failing the substitution and removing the candidate from the overload set.

It seems the C++ standard also specifies std::begin and friends with the trailing return type, possibly for the same reason.

In that sense LGTM.

Edit: To add why this makes a difference: SFINAE is only relevant during the template substitutions of the function signature, not the body. Having the begin call in the signature leads to it being a substitution failure, not an error. If that were not there and the return type remained auto, it'd have to instantiate the function body and then error out on no fitting begin existing.

This revision is now accepted and ready to land.Feb 27 2023, 4:02 AM
dblaikie accepted this revision.Feb 27 2023, 5:10 PM

Sounds OK - probably worth splitting up similar work in the future, I think there's enough nuance that understanding and testing each axis/feature in a change like this could be clearer when done separately.

This revision was landed with ongoing or failed builds.Feb 27 2023, 5:31 PM
This revision was automatically updated to reflect the committed changes.