Search code examples
c++templatesdecltype

Decltype on static variable in template class


I'm trying to define a static variable of a templated class outside of the class scope using clang:

class Bar
{
public:
    float a;
};

template<long count>
class Foo {
public:
    static Bar* test;
};

template<long count>
decltype(Foo<count>::test) Foo<count>::test; // error

int main() {
    Foo<5> f;
    return 0;
}

But I get the following error: error: redefinition of 'test' with a different type: 'decltype(Foo<count>::test)' vs 'Bar *'

It looks to me like decltype(Foo<count>::test) should evaluate to Bar *.

This code works fine on MSVC.

My question: Is there anyway to get decltype to correctly determine the type here?

In the actual code, decltype is defined in a macro that has some additional keywords depending on the configuration used, so I would like to get this working while still using decltype.


Solution

  • This code is ill-formed, because decltype(Foo<count>::test) denotes a unique type, even though it is equivalent to Bar*.

    The relevant standard sections are as follows:

    Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless [...]

    - basic.scope.scope §4

    We are even shown an example that declarations don't have to be symbolically identical to correspond:

    typedef int Int;
    /* ... */
    void f(int);                    // #1
    void f(Int) {}                  // defines #1
    

    - basic.scope.scope - example 2

    However, decltype is special in this regard:

    If an expression e is type-dependent, decltype(e) denotes a unique dependent type. Two such decltype-specifiers refer to the same type only if their expressions are equivalent ([temp.over.link]).

    - temp.type §4

    In your example:

    // variable type declared as Bar* elsewhere
    template<long count>
    decltype(Foo<count>::test) Foo<count>::test;
    

    decltype(Foo<count>::test) is dependent on count, and so the type is unique and different from Bar*. Proving that they are the same for arbitrary expressions is undecidable in the general case, so it makes sense that compilers disallow this.

    GCC falsely compiles your example (see Compiler Explorer), but clang and MSVC don't. This is probably because GCC doesn't treat test as dependent, since it is declared with type Bar*, which is not dependent. However, that is not conforming.

    Conclusion

    Your best course of action is finding a way to define this out-of-line with the same type:

    template<long count>
    Bar* Foo<count>::test;
    

    If you can't use Bar* directly, maybe there is another way for you to make your use of decltype not depend on count.

    Alternatively, you could also define the static member inline (since C++17):

    template<long count>
    class Foo {
    public:
        static inline Bar* test = ...;
    };
    

    Note: I've personally run into a similar issue with the compiler being unable to match out-of-line definitions of member functions to the declarations in the class.