Search code examples
c++gccclang

Why do I get a "for declaration does not refer into a class, class template or class template partial specialization" on Clang and not GCC


I currently have this block of code which does not throw a compiler error on GCC, but throws this compiler error on clang:

error: nested name specifier 'MyEnum::' for declaration does not refer into a class, class template or class template partial specialization

I'm not actually too sure why this is the case. In the code example below, I have offered some potential solutions to not have the error being thrown, and my particular use case. Here's the godolt link for a compiled example: https://godbolt.org/z/57aMreM3K

#include <stdexcept>

enum class MyEnum : int {
        MIN                  = 0,
        MAX                  = 1
};

class TestClass {
    public:
        explicit TestClass(const MyEnum type) { 
            throw std::runtime_error("Runtime Error");
        }
};

int main() {
    try {
        TestClass(MyEnum::MIN);
    } catch(...) { }
    
    // My usecase was to use google test to make sure there's no exception on construction
    // EXCEPT_NO_THROW(TestClass(MyEnum::MIN));

    // Potential solutions that compile for both GCC/Clang:
    // EXCEPT_NO_THROW(auto variable = TestClass(MyEnum::MIN));
    // EXCEPT_NO_THROW(TestClass((MyEnum)MyEnum::MIN));
}

Solution

  • Clang is correct here.

    There is an ambiguity in the base C++ grammar for your statement

        TestClass(MyEnum::MIN);
    

    Knowing only that TestClass is a valid type-name, but not performing any lookup aside from that, this could be either:

    1. An expression statement which consists of a functional style explicit cast expression that casts the result of the id-expression MyEnum::MIN to TestClass.

    2. A declaration with type-specifier TestClass, without any initializer and with parenthesized qualified declarator-id MyEnum::MIN. Note that in C and C++ declarators can always be wrapped in parentheses without change in meaning, e.g. int (x) = 5; is a valid declaration of a variable x of type int initialized to 5.

    You want the first meaning. However, the ambiguity is always resolved in favour of a declaration based purely on the syntactic form of the statement and whether or not constituents are valid type-name. See [stm.ambig] in the standard (draft) for the exact rules.

    In particular, the declaration interpretation is syntactically possible, but semantically ill-formed, because you can't (re-)declare the enumerator MyEnum::MIN.

    But since the semantics are not considered in the disambiguation, the interpretation as declaration must be chosen and the program is ill-formed as a consequence of the declaration being ill-formed.

    That GCC doesn't diagnose the issue is an open bug, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62116.


    An easy way of preventing ambiguities of this kind from arising is to use curly braces instead of parentheses for functional-style explicit casts:

    TestClass{MyEnum::MIN};
    

    Depending on what you want to test this might not be a solution, because it can change the meaning of the expression.

    Some other possibilities to disambiguate as an expression statement which do not affect the meaning of your expression are:

     (void)TestClass(MyEnum::MIN);
     static_cast<void>(TestClass(MyEnum::MIN));
     (TestClass(MyEnum::MIN));
     void(), TestClass(MyEnum::MIN);
     TestClass(MyEnum::MIN), void();