Search code examples
c++templatescompiler-errorsnamespacesforward-declaration

forward declaration of a class template including a namespace causes compile-error


I have an example1 and an example2, which uses a forward declaration of a class template prefixed with the namespace of the corresponding class template. The first example compiles fine with visual-studio, while the second example does not. I checked the both examples against other compilers (http://rextester.com). Now I have 2 questions:

  • Using here a namespace in the forward declaration seems to be illegal. Why exactly?

  • Visual studio (2015 and 2017) seems to allow the forward declaration with the additional namespace in the first example, but in the second example not. Is this a bug?

Example 1:

#include <vector>

namespace N1
{
    template <typename T> struct MySystem {};
    template <typename T> class Other {};

    struct MyClass
    {
        MyClass() { typename Dependencies::TYPE_A oTYPE_A; }

        struct Dependencies
        {
            template <typename>
            class N1::Other;

            struct TypeX_Dependencies;

            using TYPE_A = N1::MySystem<N1::Other<TypeX_Dependencies>>;

            struct TypeX_Dependencies
            {
                using TYPE_A = typename Dependencies::TYPE_A;
            };

            using TYPE_X = N1::Other<TypeX_Dependencies>;
        };
    };
}

int main(){ return 0; }

c++ (gcc 5.4.0)
source_file.cpp:15:23: error: invalid use of template-name ‘N1::Other’ without an argument list class N1::Other;

c++ (clang 3.8.0)
source_file.cpp:15:23: error: non-friend class member 'Other' cannot have a qualified name class N1::Other;
source_file.cpp:15:19: error: forward declaration of class cannot have a nested name specifier class N1::Other;

c++ (vc++ 19.00.23506 for x64 / also vs community 2017 15.4.4)
compiles fine

Example 2:

#include <vector>

namespace N1
{
    template <typename T> struct MySystem {};
    template <typename T> class Other {};

    template <typename T>
    struct MyClass
    {
        MyClass() { typename Dependencies::TYPE_A oTYPE_A; }

        struct Dependencies
        {
            template <typename>
            class N1::Other;

            struct TypeX_Dependencies;

            using TYPE_A = N1::MySystem<N1::Other<TypeX_Dependencies>>;

            struct TypeX_Dependencies
            {
                using TYPE_A = typename Dependencies::TYPE_A;
            };

            using TYPE_X = N1::Other<TypeX_Dependencies>;
        };
    };
}

int main(){ return 0; }

c++ (gcc 5.4.0)
source_file.cpp:16:23: error: invalid use of template-name ‘N1::Other’ without an argument list class N1::Other;

c++ (clang 3.8.0)
source_file.cpp:16:23: error: non-friend class member 'Other' cannot have a qualified name class N1::Other;
source_file.cpp:16:19: error: forward declaration of class cannot have a nested name specifier class N1::Other;

c++ (vc++ 19.00.23506 for x64 / also vs community 2017 15.4.4)
source_file.cpp(16): error C3855: 'N1::Other': template parameter 'T' is incompatible with the declaration
source_file.cpp(24): note: see reference to class template instantiation 'N1::MyClass<T>::Dependencies' being compiled
source_file.cpp(29): note: see reference to class template instantiation 'N1::MyClass<T>' being compiled
source_file.cpp(20): error C3203: 'Other': unspecialized class template can't be used as a template argument for template parameter 'T', expected a real type


Solution

  • Yes, this is a bug.

    According to [dcl.type.elab]/1,

    ... If an elaborated-type-specifier is the sole constituent of a declaration, the declaration is ill-formed unless it is an explicit specialization, an explicit instantiation or it has one of the following forms:

    • class-key attribute-specifier-seqopt identifier ;
    • friend class-key ::opt identifier ;
    • friend class-key ::opt simple-template-id ;
    • friend class-key nested-name-specifier identifier ;
    • friend class-key nested-name-specifier templateopt simple-template-id ;

    your declaration class N1::Other is neither an explicit specialization, nor an explicit instantiation, so it has to have the emphasized form (ignoring those friend declaration). Note no nested-name-specifier is allowed before identifier in the emphasized form, so the declaration is ill-formed. The compiler error of Clang in the following example without template shows this problem.

    namespace N {
        struct S;
    }
    struct N::S; // error: forward declaration of struct cannot have a nested name specifier
    

    LIVE EXAMPLE (BTW, GCC accepts this code and just gives a warning about nothing new to be declared. I guess it is a bug of GCC.)

    Here the template-head template <typename> does not help, because class N1::Other itself should form a declaration according to the grammar definition of template-head, thus the paragragh above applies.

    Roughly speaking, a declaration for class in different scope from the scope where a class with the same name is declared should either introduce a new class, in which case nested-name-specifier should not be used, or define the previously declared class, in which case the grammar forms a class-specifier rather than an elaborated-type-specifier, thus the paragraph above does not apply. As a conclusion, this rule is reasonable.