Bug 115497 - [15 Regression] __is_pointer doesn't compile with clang since 014879ea4c86b3b8ab6b61a1226ee5b31e816c8b
Summary: [15 Regression] __is_pointer doesn't compile with clang since 014879ea4c86b3b...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 15.0
: P3 normal
Target Milestone: 15.0
Assignee: Jonathan Wakely
URL: https://gcc.gnu.org/pipermail/gcc-pat...
Keywords: patch
Depends on: 109150
Blocks:
  Show dependency treegraph
 
Reported: 2024-06-14 20:21 UTC by Mital Ashok
Modified: 2024-06-21 16:13 UTC (History)
6 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2024-06-14 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Mital Ashok 2024-06-14 20:21:02 UTC
In libstdc++-v3/include/bits/cpp_type_traits.h, `__is_pointer` is both used as a unary type trait and the name of a template struct. After it is a struct template's name, Clang no longer parses it as a builtin and `inline constexpr bool is_pointer_v = __is_pointer(_Tp);` in libstdc++-v3/include/std/type_traits fails to compile.

This only happens if cpp_type_traits.h is included before type_traits, which unfortunately happens with this program: https://godbolt.org/z/afbvddddK

    #include <vector>

(Please advise if you think this is a Clang bug, how libstdc++ expects these type trait names to be parsed)
Comment 2 Andrew Pinski 2024-06-14 20:38:39 UTC
Ok, clang behavior is an odd one here:
```
template<typename _Tp, bool _IsPtr = __is_pointer(_Tp)>
  struct __is_pointer {};

template<typename _Tp>
inline bool t = __is_pointer(_Tp);

template<bool>
struct ts{};

template<typename _Tp>
  struct is_pointer : ts<__is_pointer(_Tp)> {};

int tt = t<int>;
```

clang does not complain about the use of __is_pointer for variable template t but does for the base class of is_pointer.
Comment 3 Andrew Pinski 2024-06-14 20:44:18 UTC
(In reply to Andrew Pinski from comment #2)
> clang does not complain about the use of __is_pointer for variable template
> t but does for the base class of is_pointer.

Filed that as https://github.com/llvm/llvm-project/issues/95598
Comment 4 Mital Ashok 2024-06-14 20:56:44 UTC
It appears that Clang is parsing it as a broken function type, but it should then retry parsing as an expression.

Forcing it to be an expression in the first place does let it compile:

    template<typename _Tp>
      struct is_pointer : ts<static_cast<bool>(__is_pointer(_Tp))> {};
Comment 5 Jonathan Wakely 2024-06-14 20:57:22 UTC
I suppose we can just include <type_traits> at the top of <bits/cpp_type_traits.h>, we could even make that conditional on __clang__
Comment 6 Jonathan Wakely 2024-06-14 20:58:38 UTC
(In reply to Mital Ashok from comment #4)
> Forcing it to be an expression in the first place does let it compile:
> 
>     template<typename _Tp>
>       struct is_pointer : ts<static_cast<bool>(__is_pointer(_Tp))> {};

Ah nice, we can do that
Comment 7 Andrew Pinski 2024-06-14 21:33:32 UTC
Note I think we should workaround this.

I only posted the clang issue because clang was acting inconsistent in its handling and should be more consistent in there; not that libstdc++ should not workaround the issue.
Comment 8 Arthur O'Dwyer 2024-06-15 12:58:51 UTC
FWIW, I also ran into this, this morning.
Reduced: https://godbolt.org/z/rxYKTqKd4
Organically in Abseil: https://godbolt.org/z/9eoqKzYjc
(This happens because Abseil #includes <algorithm> before <type_traits> (alphabetically). If you switch the order of those two includes, it Just Works.)

IMVHO libstdc++ should do jwakely's idea of #include <type_traits> from the offending header file; that's the most foolproof O(1) solution, rather than the O(N) solution of being really careful to always write `bool(__is_foo(T))` instead of `__is_foo(T)`. Also, there are definitely third-party libraries (like Abseil) out there who legitimately want to use `__is_pointer(T)` in their own code, and right now they have no idea that they ought to write all their uses with an extra `bool` cast just to work around a problematic interaction between Clang and libstdc++ (a mode they probably don't even build in their CI). So fixing the problem once and for all on libstdc++'s side of the fence would be really helpful.
Comment 9 Jonathan Wakely 2024-06-15 16:56:22 UTC
Changing libstdc++ order of includes won't help abseil. If their use of __is_pointer still comes after any standard header has included cpp_type_traits.h, the identifier will be "poisoned" by the class template. So they need their own workaround.
Comment 10 Jonathan Wakely 2024-06-15 16:57:05 UTC
The only foolproof fix would be to rename the __is_pointer class template.
Comment 11 Jonathan Wakely 2024-06-15 16:59:28 UTC
Although that class template has been there for years, so if any library like abseil was using the built-in, they're already have the problem that libstdc++ now has.
Comment 12 Andrew Pinski 2024-06-15 18:23:07 UTC
(In reply to Jonathan Wakely from comment #11)
> Although that class template has been there for years, so if any library
> like abseil was using the built-in, they're already have the problem that
> libstdc++ now has.

It looks like the abseil code is not using __is_pointer directly at all, but rather the standard headers here. I suspect we should just rename the __is_pointer template to workaround this clang oddness (and add a comment on why the name is not __is_pointer).
Comment 13 Richard Smith 2024-06-18 20:00:55 UTC
(In reply to Jonathan Wakely from comment #10)
> The only foolproof fix would be to rename the __is_pointer class template.

Yes, given that Clang treats this identifier as a keyword, the best solution would be for libstdc++ to stop using it as a type name. We have an awful hack in Clang to support libstdc++ doing this, and it'd be great to remove that eventually. (I apologize, we should have made this request years ago when we added the hack.)

As is evidenced in this bug, this hack was only made to be good enough to support libstdc++ as it existed at the time. I'm not really sure what behavior one would expect from `__is_pointer(_Tp)`, given that it can parse as a type (treating `__is_pointer` as a placeholder for CTAD, which is semantically invalid in this context only because we can't perform CTAD here). We could extend our hack to support this too, but doing so is really a losing proposition.

In general, we seem to have organically adopted the convention that `std::blah<...>` type traits correspond to `__blah` keywords, so it'd be great if libstdc++ didn't use any of those identifiers as type names.
Comment 14 Jonathan Wakely 2024-06-18 20:15:25 UTC
I assume clang doesn't have __is_arithmetic, __is_scalar and __is_void built-ins yet, because <bits/cpp_type_traits.h> also defines class templates with those names.

At least __are_same, __is_integer and __is_floating don't clash.
Comment 15 Richard Smith 2024-06-18 23:00:26 UTC
(In reply to Jonathan Wakely from comment #14)
> I assume clang doesn't have __is_arithmetic, __is_scalar and __is_void
> built-ins yet, because <bits/cpp_type_traits.h> also defines class templates
> with those names.

Clang has all of those (https://github.com/llvm/llvm-project/blob/12cf0dc685a9c3adfefc3a58f0b8ed4360be8b14/clang/include/clang/Basic/TokenKinds.def#L552), but it looks like <type_traits> happens to not use them in a way that breaks.

The specific problem with __is_pointer in <type_traits> is that it's being used in a template argument:

    : public __bool_constant<__is_pointer(_Tp)>

... where it would be valid to parse a type. Clang's hack to treat `__is_pointer(T)` as the builtin (after we've seen a declaration using the same name) only applies in contexts where we know we're parsing an expression. (We can fix this, but I think the healthier thing long-term is to treat these as keywords everywhere.)
Comment 16 Jonathan Wakely 2024-06-20 15:40:04 UTC
Patches series to eradicate __is_pointer, __is_scalar and __is_void posted:
https://gcc.gnu.org/pipermail/gcc-patches/2024-June/655268.html
Comment 17 Jonathan Wakely 2024-06-21 09:31:45 UTC
And this one is needed before that patch series:
https://gcc.gnu.org/pipermail/gcc-patches/2024-June/655267.html
Comment 18 GCC Commits 2024-06-21 16:07:37 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:b3743181899c5490a94c4dbde56a69ab77a40f11

commit r15-1549-gb3743181899c5490a94c4dbde56a69ab77a40f11
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Jun 19 16:14:56 2024 +0100

    libstdc++: Fix std::fill and std::fill_n optimizations [PR109150]
    
    As noted in the PR, the optimization used for scalar types in std::fill
    and std::fill_n is non-conforming, because it doesn't consider that
    assigning a scalar type might have non-trivial side effects which are
    affected by the optimization.
    
    By changing the condition under which the optimization is done we ensure
    it's only performed when safe to do so, and we also enable it for
    additional types, which was the original subject of the PR.
    
    Instead of two overloads using __enable_if<__is_scalar<T>::__value, R>
    we can combine them into one and create a local variable which is either
    a local copy of __value or another reference to it, depending on whether
    the optimization is allowed.
    
    This removes a use of std::__is_scalar, which is a step towards fixing
    PR 115497 by removing std::__is_pointer from <bits/cpp_type_traits.h>
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/109150
            * include/bits/stl_algobase.h (__fill_a1): Combine the
            !__is_scalar and __is_scalar overloads into one and rewrite the
            condition used to decide whether to perform the load outside the
            loop.
            * testsuite/25_algorithms/fill/109150.cc: New test.
            * testsuite/25_algorithms/fill_n/109150.cc: New test.
Comment 19 GCC Commits 2024-06-21 16:07:42 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:139d65d1f5a60ac90479653a4f9b63618509f3f9

commit r15-1550-g139d65d1f5a60ac90479653a4f9b63618509f3f9
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Jun 19 11:19:58 2024 +0100

    libstdc++: Don't use std::__is_scalar in std::valarray initialization [PR115497]
    
    This removes the use of the std::__is_scalar trait from <valarray>,
    where it can be replaced by __is_trivial. It's used to decide whether we
    can use memset to value-initialize valarray elements, but memset is
    suitable for any trivial types, because value-initializing them is
    equivalent to filling them with zeros.
    
    This is another step towards removing the class templates in
    <bits/cpp_type_traits.h> that conflict with Clang built-in names.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/115497
            * include/bits/valarray_array.h (__valarray_default_construct):
            Use __is_trivial(_Tp). instead of __is_scalar<_Tp>.
Comment 20 GCC Commits 2024-06-21 16:07:48 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:5f10547e021db3a4a34382cd067668f9ef97fdeb

commit r15-1551-g5f10547e021db3a4a34382cd067668f9ef97fdeb
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Jun 19 17:21:16 2024 +0100

    libstdc++: Stop using std::__is_pointer in <deque> and <algorithm> [PR115497]
    
    This replaces all uses of the std::__is_pointer type trait with uses of
    the new __is_pointer built-in. Since the class template was only used to
    enable some performance optimizations for algorithms, we can use the
    built-in when __has_builtin(__is_pointer) is true (which is the case for
    GCC trunk and for current versions of Clang) and just forego the
    optimization otherwise.
    
    Removing the uses of std::__is_pointer means it can be removed from
    <bits/cpp_type_traits.h>, which is another step towards fixing PR
    115497.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/115497
            * include/bits/deque.tcc (__lex_cmp_dit): Replace __is_pointer
            class template with __is_pointer(T) built-in.
            (__lexicographical_compare_aux1): Likewise.
            * include/bits/stl_algobase.h (__equal_aux1): Likewise.
            (__lexicographical_compare_aux1): Likewise.
Comment 21 GCC Commits 2024-06-21 16:07:53 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:51cc77672add517123ef9ea45335b08442e8d57c

commit r15-1552-g51cc77672add517123ef9ea45335b08442e8d57c
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Jun 19 11:19:58 2024 +0100

    libstdc++: Remove std::__is_void class template [PR115497]
    
    This removes the std::__is_void trait, as it conflicts with a Clang
    built-in. There is only one use of the trait, which can easily be
    replaced by simpler code.
    
    Although Clang has a hack to make the class template work despite using
    a reserved name, removing std::__is_void will allow that hack to be
    dropped at some future date.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/115497
            * include/bits/cpp_type_traits.h (__is_void): Remove.
            * include/debug/helper_functions.h (_Distance_traits):
            Adjust partial specialization to match void directly, instead of
            using __is_void<T>::__type and matching __true_type.
Comment 22 GCC Commits 2024-06-21 16:07:59 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:52a82359073653e312aaa5703f7e0ce339588961

commit r15-1553-g52a82359073653e312aaa5703f7e0ce339588961
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Jun 19 17:26:37 2024 +0100

    libstdc++: Remove std::__is_pointer and std::__is_scalar [PR115497]
    
    This removes the std::__is_pointer and std::__is_scalar traits, as they
    conflicts with a Clang built-in.
    
    Although Clang has a hack to make the class templates work despite using
    reserved names, removing these class templates will allow that hack to
    be dropped at some future date.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/115497
            * include/bits/cpp_type_traits.h (__is_pointer, __is_scalar):
            Remove.
            (__is_arithmetic): Do not use __is_pointer in the primary
            template. Add partial specialization for pointers.
Comment 23 Jonathan Wakely 2024-06-21 16:13:05 UTC
(In reply to Jonathan Wakely from comment #16)
> Patches series to eradicate __is_pointer, __is_scalar and __is_void posted:
> https://gcc.gnu.org/pipermail/gcc-patches/2024-June/655268.html

And pushed. That should fix the immediate problem, please reopen if you still see problems with clang (I don't have a clang trunk build handy to check).

I'll kill __is_arithmetic soonish.