llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.08k stars 11.99k forks source link

[clang] C++20 Concept in Template Specialization Should Not See Protected Member. #115838

Open xkszltl opened 2 days ago

xkszltl commented 2 days ago

This bug exists in all released clang versions with C++20 support up to trunk.

The following code defined classes Publ/Priv/Prot with different visibility of member x, and inherit into a templated class D specialized by its accessibility using C++20 concept. Boolean var has shows the specialization route.

In theory, only public member is visible, while protected/private are not. These are checked by static_assert.

Clang mistakenly thinks protected member from class Prot is accessible, and failed to compile this code at the last static_assert, while any versions of gcc and msvc works.

Even more confusing, if we do the exact same thing again, with another class Same identical to Prot, but query the concept directly before template instantiation, clang will give opposite answer.

template<typename T> concept has_x = requires(T t){{ t.x };};

class Publ { public:    int x = 0; };
class Priv { private:   int x = 0; };
class Prot { protected: int x = 0; };
class Same { protected: int x = 0; };

template<typename T> class D;
template<typename T> requires ( has_x<T>) class D<T>: public T { public: static constexpr bool has = 1; };
template<typename T> requires (!has_x<T>) class D<T>: public T { public: static constexpr bool has = 0; };

// "Same" is identical to "Prot" but queried before used.
static_assert(!has_x<Same>,  "Protected should be invisible.");
static_assert(!D<Same>::has, "Protected should be invisible.");

static_assert( D<Publ>::has, "Public should be visible.");
static_assert(!D<Priv>::has, "Private should be invisible.");
static_assert(!D<Prot>::has, "Protected should be invisible.");  // clang failed here.

int main() { return 0; }
llvmbot commented 2 days ago

@llvm/issue-subscribers-clang-frontend

Author: None (xkszltl)

This bug exists in all released clang versions with C++20 support up to trunk. The following code defined classes `Publ`/`Priv`/`Prot` with different visibility of member `x`, and inherit into a templated class `D` specialized by its accessibility using C++20 concept. Boolean var `has` shows the specialization route. In theory, only public member is visible, while protected/private are not. These are checked by `static_assert`. Clang mistakenly thinks protected member from `class Prot` is accessible, and failed to compile this code at the last static_assert, while any versions of gcc and msvc works. Even more confusing, if we do the exact same thing again, with another class `Same` identical to `Prot`, but query the concept directly before template instantiation, clang will give opposite answer. ```cc template<typename T> concept has_x = requires(T t){{ t.x };}; class Publ { public: int x = 0; }; class Priv { private: int x = 0; }; class Prot { protected: int x = 0; }; class Same { protected: int x = 0; }; template<typename T> class D; template<typename T> requires ( has_x<T>) class D<T>: public T { public: static constexpr bool has = 1; }; template<typename T> requires (!has_x<T>) class D<T>: public T { public: static constexpr bool has = 0; }; // "Same" is identical to "Prot" but queried before used. static_assert(!has_x<Same>, "Protected should be invisible."); static_assert(!D<Same>::has, "Protected should be invisible."); static_assert( D<Publ>::has, "Public should be visible."); static_assert(!D<Priv>::has, "Private should be invisible."); static_assert(!D<Prot>::has, "Protected should be invisible."); // clang failed here. int main() { return 0; } ``` - GCC 14.2 works. - https://godbolt.org/z/Pb9j5x3q8 - clang 19.1.0 failed. - https://godbolt.org/z/3E9qnxKbW - MSVC 19.40 x64 works. - https://godbolt.org/z/Tf14M4dK4 - clang-cl 18.1.0 failed. - https://godbolt.org/z/rjPoE1ve9