Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use the class-name inside the class itself as a template argument?

Tags:

c++

templates

I've got a templated class having two template paramters

template <class T, class U> class A  /* ... */

and another template class that accepts a template class with two arguments as template parameter.

template <class T, class U, template<class X, class Y> class Z>
class B
{
    typedef typename Z<T,U>::pointer pointer;
};

Is it impossible to create an instance of B in A where Z is A?

template <class T, class U>
class A  
{
public:
  B<T,U,A> foo (void) // compiler complaining here
  {
    B<T,U,A> test; // and here
    return test;
  }
};

A free function doing the same thing isn't problematic at all.

template<class T, class U>
B<T, U, A> bar (void)
{
    B<T,U,A> test;
    return test;
}

In other words: Is there any rule I didn't fell over yet that prevents me from using the name of the class I am in as a template argument?


The code is:

template <class T, class U, template<class X, class Y> class Z>
class B
{
  typedef typename Z<T,U>::pointer pointer;
};

template <class T, class U>
class A 
{
public:
  B<T,U, A> foo (void) 
  {
    B<T,U,A> test;
    return test;
  }
};

template<class T, class U>
B<T, U, A> bar (void)
{
    B<T,U,A> test;
    return test;
}

int main (void)
{
 return 0;
}

And the MSVC 2012 compiler gives a Compiler Error 3200.

'A<T,U>' : invalid template argument for template parameter 'Z', expected a class template
like image 586
Pixelchemist Avatar asked Jun 25 '13 01:06

Pixelchemist


1 Answers

Your compiler, MSVC, seems to follow the general rule laid down in §14.6.2.1/1 C++11:

A name refers to the current instantiation if it is, [...] in the definition of a class template, [...] the injected-class name [...] of the class template [...]

Inside the definition of class template A, the name A can be used because it is "injected" into the local (class) scope of A. Therefore, and famously, you can use A as well as A::A, as well as A::A::A and so on, to refer to A. By the rule quoted above, all of these expressions refer to the current instantiation (i.e. a specific type like A<int,float>), and not to the name of template A itself.

However, there is another rule, in §14.6.1/1:

Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injected-class-name can be used as a template-name or a type-name. When it is used [...] as a template-argument for a template template-parameter [...] it refers to the class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>.

(Note that in C++03, there is no such exception for template template parameters; 14.6.1/1, in fact, is worded entirely differently. Under C++03, MSVC's interpretation of the rules was probably correct.)

Given the C++11 rule, however, in the member declaration

B<T,U,A> test;

inside the definition of A, the name A is clearly used as an argument for a template template parameter and therefore must be interpreted as template name, not as type name referring to the current instantiation.

However, it is not uncommon that compilers are confused in situations like this. There are two valid ways to tell them how to interpret A:

  1. Using the so-called normal name ::A rather than the injected one:

    B<T,U,::A> test;
    

    This is possible because of §14.6.1/5 (which was 14.6.1/2c in C++03):

    When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name) is used, it always refers to the class template itself and not a specialization of the template. [...]

  2. Using the injected one explicitly, but designating it as a template:

    B<T,U,A::template A> test;
    

Both methods have been confirmed as solving this problem in MSVC.

like image 171
jogojapan Avatar answered Nov 04 '22 13:11

jogojapan