Trouble with initializing static variable generically

Hi,

For various reasons, I have a system for initializing a certain type of static variable in a generic way. This probably looks a bit strange taken out of context, but whatever.

Anyway, the following code compiles and runs just fine on all major compilers (MSVC, Clang, GCC):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

template <typename T>
struct SomeType
{
	static const int StaticVar;
};

// Initialize 'StaticVar' as 'int'
template <typename T>
const int SomeType<T>::StaticVar = 0;

int main() 
{
	cout << SomeType<int>::StaticVar << endl;
}


Cool, now I can initialize 'StaticVar' to whatever I want. But what if I want to change its type without changing how it's initialized? (again, makes more sense given the context)

1
2
3
// Initialize 'StaticVar' as ... whatever type it is
template <typename T>
const decay_t<decltype(SomeType<T>::StaticVar)> SomeType<T>::StaticVar = 0;


Now the code compiles on GCC and MSVC, but not Clang, which fails with the following error:

error: conflicting declaration 'StaticVarT<SomeType<T> > SomeType<T>::StaticVar'


What if I wanted to extract that decltype stuff into another type; after all, it might be useful somewhere else!

1
2
3
4
5
6
7
// The type of 'StaticVar' for T
template <typename T>
using StaticVarT = decay_t<decltype(T::StaticVar)>;

// Initialize 'StaticVar' as ... whatever type it is
template <typename T>
const StaticVarT<SomeType<T>> SomeType<T>::StaticVar = 0;


Now it only compiles on MSVC. Interestingly, none of these have any problem if "SomeType" is not a template.

What's going on here?
Last edited on
1
2
3
4
5
6
7
8
9
template <typename T>
struct SomeType
{
	typedef int foo_type; //or const int
	static const foo_type StaticVar;
};

template <typename T>
const typename SomeType<T>::foo_type SomeType<T>::StaticVar = 0;
Last edited on
Awesome, that solves my problem!

Any idea why that works, while my original solution does not?

Thanks
> Any idea why that works, while my original solution does not?

This is based on my interpretation of the IS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <type_traits>

template < typename T > struct stype { using type = typename T::svar_type ; };

template < typename T > struct A
{
    static const typename std::decay<T>::type svar1 ;

    using svar_type = const typename std::decay<T>::type ;
    static svar_type svar2 ;

    using svar_type_alias = typename stype< A<T> >::type ;
    static svar_type_alias svar3 ;

    static svar_type svar4 ;
    static svar_type svar5 ;

};

// fine: const typename std::decay<T>::type is a type-dependent name, and it refers to the current instantiation
template < typename T > const typename std::decay<T>::type A<T>::svar1 {} ; // value-initialised

// fine: typename A<T>::svar_type is a type-dependent name, and it refers to the current instantiation
template < typename T > typename A<T>::svar_type A<T>::svar2 {} ; // value-initialised

// fine: typename stype< A<T> >::type is a type-dependent name, and it refers to the current instantiation
template < typename T > typename stype< A<T> >::type  A<T>::svar3 {} ; // value-initialised

// *** error: though typename stype< A<T> >::type is a type-dependent name, it does not refer to the current instantiation
template < typename T > typename stype< A<T> >::type  A<T>::svar4 {} ; // value-initialised

// *** error: decltype( A<T>::svar5 ) is a value-dependent name, not a type-dependent name
template < typename T > typename std::remove_reference< decltype( A<T>::svar5 ) >::type A<T>::svar5 {} ;

int main()
{
}

http://coliru.stacked-crooked.com/a/531abb6d92e88406

It compiles cleanly with Microsoft C++ 19.0 because:
VC hasn't implemented three C++98/03 features: two-phase name lookup, dynamic exception specifications, and export. Two-phase name lookup remains unimplemented in 2015, but it's on the compiler team's list of things to do, pending codebase modernization. Dynamic exception specifications also remain unimplemented (VC gives non-Standard semantics to throw() and ignores other forms), but they were deprecated in C++11 and nobody cares about them now that we have noexcept. It's unlikely that we'll ever implement them, and there's even been talk of removing them from C++17. Finally, export was removed in C++11.
- STL in 'C++11/14/17 Features In VS 2015 RTM'

http://blogs.msdn.com/b/vcblog/archive/2015/06/19/c-11-14-17-features-in-vs-2015-rtm.aspx?PageIndex=1#comments
I asked Cubbi about this and this is his reply:

I don't think the analysis is quite right

A<T> refers to the current instantiation (by 14.6.2.1[temp.dep.type]/1.2) when used Within the definition of the template and the definitions of its members svar1..svar5
A<T>::svar_type names a member of the current instantiation (by 14.6.2.1[temp.dep.type]/5.2) when used within those definitions

but

std::decay<T>::type names a member of std::decay, which is a different class template (and if you follow through, it is an alias to T, which is neither A<T> nor its member)
stype<A<T>>::type names a member of stype<A<T>>, which is again a different class template (and again if you follow through, it is an alias to T)


What I think you're encountering here are the type equivalence rules

for svar5:
14.4[temp.type]/2 If an expression e involves a template parameter, decltype(e) denotes a unique dependent type.

For svar4:
14.4[temp.type]/1 Two template-ids refer to the same class, function, or variable if their template-names refer to the same template and...

So stype< A<T> >::type in the definition of svar3 refers to the same type as (the alias of) stype<A<T>>::type in the declaration of svar3
but stype< A<T> >::type in the definition on svar4 refers to a different type as (the alias of) std::decay<T>::type: the template-ids are different

and decltype(A<T>::svar5) is a totally unique type, not the same as the type in the declaration of svar5
oh, and if 14.5.6.1[temp.over.link]/6 applies to member functions of class templates (I am not sure it does, it could be a good stackoverflow question), then MSVC is not even wrong, since it's ill-formed no diagnostic required to declare function templates that are functionally equivalent, but not equivalent. (as the declarations of svar4 and svar5 are)

Related (for function templates, not members of class templates) http://stackoverflow.com/questions/9607089
Topic archived. No new replies allowed.