Thursday, 24 January 2013

C++ Templates - POI

In this post, I will briefly discuss about some of the key concepts related to templates.

// Primary Template
template <typename T = bool, typename U = int, typename V = long>
class Bark
{
public:
    void bark() {
        std::cout << 0;
    }
};
// Partial Specialization
template <typename U, typename V> class Derived : public Bark<bool, U, V> // <----Line1 { public: void bark() { this->Bark<bool, U, V>::bark(); std::cout << 1; } }; #if 1 template<typename U, typename V> // <----Line2 class Bark<bool, U, V> { public: void bark() { static_assert(std::is_same<U, int>::value, "Types don't match"); static_assert(std::is_same<V, long>::value, "Types don't match"); std::cout << 2; } }; #endif
// <----POIs (2) (3) (1)                   // <----Line3
int main() { Derived<int, long> d; d.bark(); // This instantiates the partial specialization // of class Base Bark<bool> b; b.bark(); // prints 2 }

In the code snippet above, we define the primary class template Bark that has three type parameters each having a default value.

The source code also defines a partial template specialization of the primary class template Bark where the type parameter T is fixed as 'bool'. There is nothing esoteric about this except for noticing the fact that this partial class template specialization can be instantiated without specifying the type arguments for U and V. This is because, the partial class template specialization for Bark picks up the default parameters for U and V from the primary class template. Notice the type Bark<bool> used to define 'b' in main which just specifies only one argument (the first argument) while instantiating the partial class template specialization.

This important principle is used in several applications of templates. One notable use of this property is in the usage of the class template enable_if.

The second concept illustrated in the code pertains to the concept of what is known as the Point of Instantiation. There are points in the source of template clients where a C++ compiler must have access to the declaration or the definition of a template entity. These are called POI (Point of Instantiation).

When the compiler sees the declaration of 'd' in main, it tries to instantiate the primary class template Bark. Instantiation of this implicitly requires instantiating a specialization of Bark with T = bool. However, the explicitly declared partial specialization of Bark for T = bool on Line 2 is not visible at Line1. The compiler can however try to explicitly generate a partial specialization of Bark for T = bool. However, that would then lead to duplicate declaration later on Line2.

The C++ standard has clear rules (very terse to read) for Point of instantiation of templates. Let's understand some of the rules applicable in our code example.

There are three POIs in our code sample. These are

1) POI for the class template Derived<U, V>. Note that Derived is a class template

2) POI for the specialization of Bark<bool, U, V> and,

3) POI for the specialization of the member function Bark<bool, U, V>::bark

The C++ standard specifies that the
 
1) POI for Derived<U, V> is just before the beginning of the namespace scope function 'main' which refers to the instantiation of Derived<U, V>. This can be considered as the line marked as Line3, just before the 'main' function.

2) POI for specialization of Bark<bool, U, V> is just before the POI of 1) above

3) POI for specialization of the member function Bark<bool, U, V>::bark is the same as that of 2) above

The commented line just before 'main' indicates that the POI for (2) and (3) are the same, and the POI for (1) follows the POI for (2) and (3).

The guiding clauses for the above observations are as follows:

a) $14.6.4.1/2 - "....Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization."

b) $14.6.4.1/2 - "For a class template specialization...if the context from which the specialization is referenced depends on a template parameter,...the point of instantiation is immediately before the point of instantiation of the enclosing template."

c) $14.6.4.1/1 - "...or a specialization for a member function...the point of instantiation of the specialization is the point of instantiation of the enclosing specialization."

With the POIs clearly defined, the compiler now sees the partial specialization of the primary template 'Bark' on Line2 while instantiating Derived<U, V>. This is because the user specified partial specialization Bark<bool, U, V> is visible from the POI of Derived<U, V> which is (c) on Line3.

Note that the declaration Bark<bool> partial specialization is not instantiated again while defining 'b' in 'main'. This is because, the partial specialization Bark<bool, U, V> has already been instantiated while instantiating the Derived<int, long>.

It is important to realize that if the code within #if1 is put under #if 0 (thereby suppressing the definition of the  user specified partial class specialization), the resulting code is still valid. In this case, once again all the POIs are on Line3, but since no user provided partial specialization of Bark is visible, the compiler generates the partial specialization of Bark for T = bool from the primary class template Bark<T, U, V>

No comments:

Post a Comment