llvm / llvm-project

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

"private" access specifier not respected in overloaded SFINAE context #107629

Open HexadigmSystems opened 3 weeks ago

HexadigmSystems commented 3 weeks ago

Given the following code (run it here), where "Base::Whatever" is private (both overloads), why does Clang correctly display false except when (both) "T" is "Derived" and function "Whatever" is overloaded. This appears to be erroneous behavior but it's a fuzzy area in this context (but see behavior table further below):


#include <type_traits>
#include <iostream>

class Base
{
private: // Defaults to this anyway but being explicit

    void Whatever(int)
    {
    }

    void Whatever(int, float)
    {
    }
};

class Derived : public Base
{
};

template <typename T, typename U, typename = void>
struct HasFuncWhatever : std::false_type
{
};

template <typename T, typename U>
struct HasFuncWhatever<T,
                       U,
                       std::void_t<decltype(static_cast<U T::*>(&T::Whatever))>
                      >
                      : std::true_type
{
};

int main()
{
    using T = Derived;
    using U = void (int);
    std::cout << std::boolalpha << HasFuncWhatever<T, U>::value;
    return 0;
}

Here's the behavior of the 3 compilers I tested (only MSVC presumably gets it right):

T          "Whatever" overloaded?   Clang Displays    MSVC Displays     GCC Displays       
-          ----------------------   --------------    -------------     ------------
Base       No                       false (correct)   false (correct)   false (correct)    
Base       Yes                      false (correct)   false (correct)   Fails compilation due to private access (incorrect)
Derived    No                       false (correct)   false (correct)   false (correct)  
Derived    Yes                      true (incorrect)  false (correct)   Fails compilation due to private access (incorrect)

Unless this is explicitly mentioned in the standard somewhere, or it's considered undefined behavior (implementation defined), the call to "&T::Whatever" in the partial specialization of "HasFuncWhatever" should always presumably fail since "Whatever" is private. The primary template should therefore always kick in so the code should always display false.

llvmbot commented 3 weeks ago

@llvm/issue-subscribers-clang-frontend

Author: Hexadigm Systems (HexadigmSystems)

Given the following code (run it [here](https://godbolt.org/z/WTdbqdjc5)), where "Base::Whatever" is private (both overloads), why does Clang correctly display false except when "T" is "_true_" and function "_Whatever_" is overloaded. This appears to be erroneous behavior but it's a fuzzy area in this context (but see behavior table further below): ``` #include <type_traits> #include <iostream> class Base { private: // Defaults to this anyway but being explicit void Whatever(int) { } void Whatever(int, float) { } }; class Derived : public Base { }; template <typename T, typename U, typename = void> struct HasFuncWhatever : std::false_type { }; template <typename T, typename U> struct HasFuncWhatever<T, U, std::void_t<decltype(static_cast<U T::*>(&T::Whatever))> > : std::true_type { }; int main() { using T = Derived; using U = void (int); std::cout << HasFuncWhatever<T, U>::value; return 0; } ``` Here's the behavior of the 3 compilers I tested (only MSVC presumably gets it right): Clang behavior ``` T "Whatever" overloaded? Clang Displays MSVC Displays GCC Displays - ---------------------- -------- -------- -------- Base No false (correct) false (correct) false (correct) Base Yes false (correct) false (correct) Fails compilation due to private access (incorrect) Derived No false (correct) false (correct) false (incorrect) Derived Yes true (incorrect) false (correct) Fails compilation due to private access (incorrect) ``` Unless this is explicitly mentioned in the standard somewhere, or it's considered undefined behavior (implementation defined), the call to "&T::Whatever" in the partial specialization of "HasFuncWhatever" should always presumably fail since "Whatever" is private. The primary template should therefore always kick in so the code should always display false.
MitalAshok commented 3 weeks ago

This happens in non-SFINAE contexts too:

https://godbolt.org/z/sKvqq1qYx:

struct Base {
private:
  int f();
  int f(int); // Does not compile if this is removed
};

struct Derived : public Base {};

int main() {
  int(Derived::* _)() = &Derived::f;
}
HexadigmSystems commented 3 weeks ago

Thanks for the quick reply. Definitely simplifies things, and I probably should have checked but was focused on my specific issue. In any case, it appears to be a bug unless someone can confirm otherwise (may be implementation defined behavior but looks very suspect and definitely inconvenient).

shafik commented 3 weeks ago

No one allows this case: https://godbolt.org/z/c4f5K18v1

struct Base {
private:
  int f();
  int f(int); // Does not compile if this is removed
};

struct Derived : public Base {};

int main() {
  int(Base::* _)() = &Base::f;
}
HexadigmSystems commented 3 weeks ago

Thanks for the feedback. Didn't really look into MitalAshok's post in too much detail (though you've changed it a bit) but my original post occurs in an SFINAE context so it's a murky area. On the surface Clang and GCC look problematic but a language lawyer may be required to settle it.

Backl1ght commented 3 weeks ago

diag is suppressed here https://github.com/llvm/llvm-project/blob/dec0781c8b06b1a5b8ad8646d299f643745a36d0/clang/lib/Sema/SemaExpr.cpp#L3176-L3207

HexadigmSystems commented 3 weeks ago

@Backl1ght Thanks for the info (does it mean it's now a confirmed bug?). Note that I reported GCC's own issue here (as described in the behavior table of my original post above). That bug was confirmed by GCC (see Andrew Pinski's response at latter link). Someone there also added a link to this Clang post in the See Also section.