19.3 Identifying Function Types

Ru-Brd

The problem with function types is that because of the arbitrary number of parameters, there isn't a finite syntactic construct using template parameters that describes them all. One approach to resolve this problem is to provide partial specializations for functions with a template argument list that is shorter than a chosen limit. The first few such partial specializations can be defined as follows :

  // types/type5.hpp  template<typename R>  class CompoundT<R()> {    public:      enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,             IsFuncT = 1, IsPtrMemT = 0 };      typedef R BaseT();      typedef R BottomT();      typedef CompoundT<void> ClassT;  };  template<typename R, typename P1>  class CompoundT<R(P1)> {    public:      enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,             IsFuncT = 1, IsPtrMemT = 0 };      typedef R BaseT(P1);      typedef R BottomT(P1);      typedef CompoundT<void> ClassT;  };  template<typename R, typename P1>  class CompoundT<R(P1, ...)> {    public:      enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,             IsFuncT = 1, IsPtrMemT = 0 };      typedef R BaseT(P1);      typedef R BottomT(P1);      typedef CompoundT<void> ClassT;  };   

This approach has the advantage that we can create typedef members for each parameter type.

A more general technique uses the SFINAE (substitution-failure-is-not-an-error) principle of Section 8.3.1 on page 106: An overloaded function template can be followed by explicit template arguments that are invalid for some of the templates. This can be combined with the approach used for the classification of enumeration types using overload resolution. The key to exploit SFINAE is to find a type construct that is invalid for function types but not for other types, or vice versa. Because we are already able to recognize various type categories, we can also exclude them from consideration. Therefore, one construct that is useful is the array type. Its elements cannot be void , references, or functions. This inspires the following code:

 template<typename T>  class IsFunctionT {    private:      typedef char One;      typedef struct { char a[2]; } Two;      template<typename U> static One test(...);      template<typename U> static Two test(U (*)[1]);    public:      enum { Yes = sizeof(IsFunctionT<T>::test<T>(0)) == 1 };      enum { No = !Yes };  }; 

With this template definition, IsFunctionT<T>::Yes is nonzero only for types that cannot be types of array elements. The only shortcoming of this observation is that this is not only the case for function types, but it is also the case for reference types and for void types. Fortunately, this is easily remedied by providing partial specialization for reference types and explicit specializations for void types:

 template<typename T>  class IsFunctionT<T&> {    public:      enum { Yes = 0 };      enum { No = !Yes };  };  template<>  class IsFunctionT<void> {    public:      enum { Yes = 0 };      enum { No = !Yes };  };  template<>  class IsFunctionT<void const> {    public:      enum { Yes = 0 };      enum { No = !Yes };  };   

Various alternatives exist. For example, a function type F is also unique in that a reference F& implicitly converts to F* without an user -defined conversion.

These considerations allow us to rewrite the primary CompoundT template as follows:

  // types/type6.hpp  template<typename T>  class IsFunctionT {    private:      typedef char One;      typedef struct { char a[2]; } Two;      template<typename U> static One test(...);      template<typename U> static Two test(U (*)[1]);    public:      enum { Yes = sizeof(IsFunctionT<T>::test<T>(0)) == 1 };      enum { No = !Yes };  };  template<typename T>  class IsFunctionT<T&> {    public:      enum { Yes = 0 };      enum { No = !Yes };  };  template<>  class IsFunctionT<void> {    public:      enum { Yes = 0 };      enum { No = !Yes };  };  template<>  class IsFunctionT<void const> {    public:      enum { Yes = 0 };      enum { No = !Yes };  };  // same for  void volatile  and  void const volatile   template<typename T>  class CompoundT {  // primary template  public:      enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,             IsFuncT = IsFunctionT<T>::Yes,             IsPtrMemT = 0 };      typedef T BaseT;      typedef T BottomT;      typedef CompoundT<void> ClassT;  }; 

This implementation of the primary template does not exclude the specializations proposed earlier, so that for a limited number of parameters the return types and the parameter types can be accessed.

An interesting historical alternative relies on the fact that at some time in the history of C++,

 template<class T>  struct X {      long aligner;      Tm;  }; 

could declare a member function X::m() instead of a nonstatic data member X::m (this is no longer true in standard C++). On all implementations of that time, X<T> would not be larger than the following X0 type when T was a function type (because nonvirtual member functions don't increase the size of a class in practice):

 struct X0 {      long aligner;  }; 

On the other hand, X<T> would be larger than X0 if T were an object type (the member aligner was required because, for example, an empty class typically has the same size as a class with just a char member).

With all this in place, we can now classify all types, except class types and enumeration types. If a type is not a fundamental type and not one of the types recognized using the CompoundT template, it must be an enumeration or a class type. In the following section, we rely on overload resolution to distinguish between the two.

Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net