Main Content

AUTOSAR C++14 Rule A14-8-2

Explicit specializations of function templates shall not be used

Since R2020a

Description

Rule Definition

Explicit specializations of function templates shall not be used.

Rationale

Explicit specialization of function templates can cause unexpected issues with overload resolution in C++. Overload resolution:

  • First searches for a non-template, plain-old-function that has a matching parameter list.

  • If such a function is not available, overload resolution selects the closest matching function template.

  • After a function template is selected, the compiler searches for a suitable specialization of the selected template.

Specializing a template does not change the order of the overload resolution process, which can result in confusing and unexpected behavior. Consider this code snippet:

//(a) base template
template<class T> void f( T ); 

//(b) specialization of (a)
template<> void f<>(int*);
//...

//(c) overloads (a)
template<class T> void f( T* );

//...
main(){
	int *p; 
	f( p );
}
When f() is called with an int* in main(), you might expect the specialization for int*, marked (b), to be called. The compiler resolves the call to f() as follows:

  1. The compiler searches for a plain-old-function with input type int*.

  2. Because there is no such function, the compiler searches for a function template that has the closest matching parameter list.

  3. The template (c), which takes a generic pointer as input, is the closest match for f(int*).

  4. The template (c) is not specialized. The overload resolution process stops and calls the template in (c).

Even though a specialized template for int* type input is defined in (b), the function call resolves to the template in (c) instead, which can be unexpected.

When you specialize an overloaded function template, which among the overloaded templates gets specialized depends on the order of declaration. Consider the code snippet:

//(a)
template <typename T> void F1(T t){}
//(b)
template <typename T> void F1(T* p){}
//(x): Specialization of template
template <> void F1<>(uint16_t* p){}
You cannot determine whether (x) specializes (a) or (b) from the declaration alone. it depends on the declaration order. For instance, in the preceding case (x) specializes (b). But in this case, (x) specializes (a):
//(a)
template <typename T> void F1(T t){}
//(x): Specialization of template
template <> void F1<>(uint16_t* p){}
//(b)
template <typename T> void F1(T* p){}

To avoid confusing code and unexpected behavior, avoid specializing function templates. If you must specialize a function template, then write a single function template that delegates to a class template. For example, in this code, a function template f() delegates to the class f_implementation.

template<class T> class f_implementation;

template<class T> void f( T t ) {
	FImpl<T>::f( t ); //Don't specialize function template
} 

template<class T> class f_implementation { 
	static void f( T t ); // Specializing class templates is permissible. 
}
Delegating to a class template also enables partial specialization.

Polyspace Implementation

If you explicitly specialize a function template, Polyspace® flags the function template.

Troubleshooting

If you expect a rule violation but Polyspace does not report it, see Diagnose Why Coding Standard Violations Do Not Appear as Expected.

Examples

expand all

This example shows how Polyspace flags specialized function templates. When the function F1() is called in main(), overload resolution determines which instance of F1() is called.

  • When F1() is invoked with pointers to uint8_t, the compiler calls the plain-old-function (d) because it takes precedence. You might incorrectly expect the specialization (y) to be called.

  • When F1() is invoked with pointers to uint16_t, the compiler calls the overloaded template (b) because it is the closest matching template. You might incorrectly expect the specialization (x) to be called.

Specializing function templates can cause confusion and unexpected results. Polyspace flags the specialized function templates (x) and (y).

#include <cstdint>
#include <memory>
#include <iostream>
//(a)
template <typename T> void F1(T t){
  std::cout << "(a)" << std::endl;
}
//(x) specializes (a)
template <> void F1<>(uint16_t* p){// Noncompliant
  std::cout << "(x)" << std::endl;
}
//(b) overloads (a)
template <typename T> void F1(T* p){// Compliant
  std::cout << "(b)" << std::endl; 
}
//(y) specializes (b)
template <> void F1<>(uint8_t* p){// Noncompliant
  std::cout << "(c)" << std::endl; 
}
//(d) plain old function overloads (a) and (b)
void F1(uint8_t* p){                    // Compliant
  std::cout << "(d)" << std::endl; 
}

int main(void)
{
	auto sp8 = std::make_unique<uint8_t>(3);
	auto sp16 = std::make_unique<uint16_t>(3);
	F1(sp8.get()); //calls (d), might expect (y) 
	F1(sp16.get()); //calls (b), might expect (x)
	return 0;
}

Check Information

Group: Templates
Category: Required, Automated

Version History

Introduced in R2020a