Template Specialization

I am being driven Nuts by a simple Template specialization problem

Here is the code, Its intent is straightforward,


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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>

using namespace std;

template<int const P,int const Q>
struct cWrapper {

	#if 0
		#define P 5
		#define Q 8
	#endif
		
	template<int A,int B> 
	struct cInner {
		static void X(int SpecifiedA,int SpecifiedB){
			cout << " Generic (" << SpecifiedA << " " << SpecifiedB << ")"  			
				 << " A == " << A 
				 << " B == " << B    				 				 				 
				 << "\n" ;    
		}
	};

 	template<>
	struct cInner<P,Q> {
		static void X(int SpecifiedA,int SpecifiedB){
			cout << " Special (" << SpecifiedA << " " << SpecifiedB << ")"  					
				 << " A == " << "P"
				 << " B == " << "Q"     				 
				 << "\n" ;    				 
		}
	};

 	template<int B>
	struct cInner<P,B> {
		static void X(int SpecifiedA,int SpecifiedB){
			cout << " Special (" << SpecifiedA << " " << SpecifiedB << ")"  							
				 << " A == " << "P"
				 << " B == " << B    				 				 
				 << "\n" ;    				 				 
		}
	};

	template<int A>
	struct cInner<A,Q> {
		static void X(int SpecifiedA,int SpecifiedB){
			cout << " Special (" << SpecifiedA << " " << SpecifiedB << ")"  									
				 << " A == " << A
				 << " B == " << "Q"    				 				 
				 << "\n" ;    				 				 
		}
	};

};

int main(){
 	cWrapper<5,8>::cInner<9,9>::X(9,9);
	cWrapper<5,8>::cInner<5,9>::X(5,9);
	cWrapper<5,8>::cInner<9,8>::X(9,8);
	cWrapper<5,8>::cInner<5,8>::X(5,8);

	return 0;
} 

Here is the output without the macro definition overriding the template
parameters (the Wrong behaviour) :-

Special (9 9) A == P B == 9
Generic (5 9) A == 5 B == 9
Generic (9 8) A == 9 B == 8
Special (5 8) A == P B == Q

Here is the output with the macro definitions overriding the template
parameters (the Right behaviour)

Generic (9 9) A == 9 B == 9
Special (5 9) A == P B == 9
Special (9 8) A == 9 B == Q
Special (5 8) A == P B == Q

I am using the MS VC++ 2008 Express, on XP all up todate, and have gotten the same behaviour from 2010 Express.

Thanks In advance, this has got me stumped, or am I missing summit.

Nick




The C++ standard does not allow explicit specialization of a member of a class at class scope.

IS 14.7.2/6:
An explicit instantiation of a class or function template specialization is placed in the namespace in which the template is defined. An explicit instantiation for a member of a class template is placed in the namespace where the enclosing class template is defined. An explicit instantiation for a member template is placed in the namespace where the enclosing class or class template is defined.


A conforming compiler should give an error for this (wrt this, the Microsoft compilers have been non-conforming):
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
template< int I, int J > struct outer
{
    // generalization
    template < int K, int L > struct inner
    {
        static void foo()
        {
            std::cout << "generalization: outer<" << I << ',' << J
                      << ">::inner<" << K << ',' << L << ">::foo\n" ;
        }
    };

    // partial specializations are allowed in this scope
    template < int K > struct inner<I,K>
    {
        static void foo()
        {
            std::cout << "partial specialization: outer<" << I << ',' << J
                      << ">::inner<" << K << ',' << K << ">::foo\n" ;
        }
    };

    // this is an error: An explicit instantiation for a member template
    // should be placed in the  namespace where the enclosing class
    // or class template is defined. IS 14.7.2/6
    template <> struct inner<I,J>
    {
        static void foo()
        {
            std::cout << "explicit specialization: outer<" << I << ',' << J
                      << ">::inner<" << I << ',' << J << ">::foo\n" ;
        }
    };
};


One work-around to allow declaration of what in effect would behave like an explicit specialization inside the class itself is to use an extra unused template parameter with a default vale:
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
39
40
template< int I, int J > struct outer
{
    // generalization
    template < int K, int L, void* NOT_USED = nullptr > struct inner
    {
        static void foo()
        {
            std::cout << "generalization: outer<" << I << ',' << J
                      << ">::inner<" << K << ',' << L << ">::foo\n" ;
        }
    };

    // partial specializations are allowed in this scope
    template < int K, void* NOT_USED > struct inner<I,K,NOT_USED>
    {
        static void foo()
        {
            std::cout << "partial specialization: outer<" << I << ',' << J
                      << ">::inner<" << K << ',' << K << ">::foo\n" ;
        }
    };

    // this is now allowed; it is a partial specialization now
    template < void* NOT_USED > struct inner<I,J,NOT_USED>
    {
        static void foo()
        {
            std::cout << "effectively explicit (technically partial)"
                      << " specialization: outer<" << I << ',' << J << ">::inner<"
                      << I << ',' << J << ">::foo\n" ;
        }
    };
};

int main()
{
    outer<2,3>::inner<4,5>::foo() ;
    outer<2,3>::inner<2,4>::foo() ;
    outer<2,3>::inner<2,3>::foo() ;
}

Thanks a lot - I get very confused reading the standard.
Very interesting...why would this be though? And what is the syntax for defining inner<I,J> in the namespace in which outer is defined?
> why would this be though?

The reasons are somewhat arcane; but primarily they are related to the way in which compilers perform name lookup inside templates. See http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html and http://www.linuxtopia.org/online_books/programming_books/c++_practical_programming/c++_practical_programming_124.html

A grossly oversimplified example:
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
template< typename T > struct A
{
    template< typename U > struct B
    {
        // ok, generalization is subject to two phase lookup rule
        // this is resolved during phase two
        typedef typename A<T>::pointer type ;
    };

    template< typename U > struct B<U*>
    {
        // ok, partial specialization too is subject to two phase lookup rule
        // this is resolved during phase two
        typedef typename A<T>::pointer* type ;
    };

    #ifdef this_is_not_allowed
    // if this were to be allowed
    template<> struct B<int>
    {
        // Ah! this is an explicit specialization. A name is "non-dependent" if
        // the results of name lookup do not depend on any template parameters
        // "non-dependent" names are looked up early (during phase one).
        // For this to work, A<int> must be a complete (and instatiated) type
        // at this point. Which it definitely is not.
        typedef typename A<int>::pointer type ;  // ???
    };
    #endif

    typedef T* pointer ;
};


So,

a. An explicit specialization/instantiation of a member of a class must be placed in the namespace where the enclosing class is defined.

b. A template member of a template class cannot be explicitly specialized without also explicitly specializing the containing class.


> And what is the syntax for defining inner<I,J> in the namespace in which outer is defined?

That is the simple part; just use nested templates:
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
#include <iostream>

template< int I > struct outer
{
    template < int J, int K > struct inner { enum { id = 0 } ;  };
};

template  < int I > template < int J > // partial spec.
struct outer<I>::inner<J,I> { enum { id = 1 } ; };


template  < int I > template < int K > // partial spec.
struct outer<I>::inner<0,K> { enum { id = 2 } ; };

template  < int I > template < int J > // partial spec.
struct outer<I>::inner<J,J> { enum { id = 3 } ; };

template  <> template <> // complete spec.
struct outer<1>::inner<2,3> { enum { id = 4 } ; };

int main()
{
    std::cout << outer<10>::inner<20,30>::id << '\n' ; // 0
    std::cout << outer<10>::inner<20,10>::id << '\n' ; // 1
    std::cout << outer<10>::inner<0,30>::id << '\n' ; // 2
    std::cout << outer<10>::inner<20,20>::id << '\n' ; // 3
    std::cout << outer<1>::inner<2,3>::id << '\n' ; // 4
}




Topic archived. No new replies allowed.