在哪些情况下需要专门指定模板的参数"类型"?

In which cases one needs to specify the template's argument `types` specifically?

本文关键字:参数 类型 情况下      更新时间:2023-10-16
// Function declaration.
template <typename T1, 
          typename T2, 
          typename RT> RT max (T1 a, T2 b);
// Function call.
max <int,double,double> (4,4.2)
// Function call.
max <int> (4,4.2)

一种情况可能是需要指定返回类型。

是否存在其他需要手动指定参数类型的情况?

(1)没有参数函数时,它仍然是template类型,那么您可能必须显式指定参数

template<typename T>
void foo ()
{}

用法:

foo<int>();
foo<A>();

(2)您希望区分值和引用

template<typename T>
void foo(T obj)
{}

用法:

int i = 2;
foo(i); // pass by value
foo<int&>(i); // pass by reference

(3)需要另一个类型来代替自然类型。

template<typename T>
void foo(T& obj)
{}

用法:

foo<double>(d);  // otherwise it would have been foo<int>
foo<Base&>(o); // otherwise it would have been foo<Derived&>

(4)单个模板形参提供了两个不同的实参类型

template<typename T>
void foo(T obj1, T obj2)
{}

用法:

foo<double>(d,i);  // Deduction finds both double and int for T

如果函数模板参数出现在函数参数列表中,则不需要指定模板参数。例如,

template<typename T>
void f(const T &t) {}

这里T是模板参数,出现在函数参数表中,即const T &t。所以在调用这个函数时不需要指定模板参数:

f(10); //ok

由于10类型int,所以编译器可以从中推导出模板形参T, T变成了int

请注意,由于类型推导是使用函数实参信息完成的,因此称为模板实参推导。请继续阅读。

如果模板参数没有出现在函数参数列表中,那么你必须提供模板参数。例子:

template<typename T>
void g(const int &i) {}

注意g()不同于f()。现在,T没有出现在函数参数列表中。所以:

g(10); //error
g<double>(10); //ok

请注意,如果函数模板也在返回类型上进行模板化,并且返回类型与函数参数列表中显示的类型不同,则必须提供返回类型:

template<typename T>
T h(const T &t) {}

由于返回类型T与函数形参相同,因此可以从函数实参进行类型推导:

h(10); //ok - too obvious now

但是如果你有这个:

template<typename R, typename T>
R m(const T &t) {}

,

m(10);  //error - only T can be deduced, not R
m<int>(10);  //ok

注意,即使函数模板m已经模板化了两种类型:RT,我们在调用它时只提供了一种类型。也就是说,我们写的是m<int>(10)而不是m<int,int>(10)。写后一个没有什么坏处,但如果你不写也没关系。但有时您必须同时指定这两种类型,即使可以推导出一种类型T。当类型参数顺序不同时如下所示:

template<typename T, typename R> //note the order : its swapped now!
R n(const T &t) {}

现在,您必须提供这两种类型:

n(10); //error - R cannot be deduced!
n<int>(10); //error - R still cannot be deduced, since its the second argument!
n<int,int>(10); //ok

这里的新东西是:类型参数的顺序也很重要

无论如何,这只涵盖了基本概念。现在我建议你们读一些关于模板的好书,学习所有关于类型演绎的高级知识。

一般情况下,当编译器无法自行确定类型时,需要显式指定类型。正如您所提到的,当返回类型被模板化时,这种情况经常发生,因为返回类型不能从函数调用中推断出来。

模板类也有同样的问题——实例化std::vector无法让编译器确定vector存储的类型,因此需要指定std::vector<int>等等。

类型解析只在函数实参的情况下执行,因此将其视为特殊情况可能更容易;通常,编译器无法猜测要使用的类型。

简单的答案是,当编译器不能自行推断类型时,或者当您希望模板实例化为与编译器将推断的类型不同的特定类型时,您需要提供这些类型。

编译器不能推断类型有不同的情况。因为类型推导只应用于参数(就像重载解析的情况一样),如果返回类型不是作为可推导的参数出现,那么您必须指定它。但在其他情况下,类型演绎将不起作用:

template <typename R> R f(); // Return type is never deduced by itself
template <typename T>
T min( T const & lhs, T const & rhs );
min( 1, 2 );                 // Return type is deducible from arguments
min( 1.0, 2 );               // T is not deducible (no perfect match)
min<double>( 1.0, 2 );       // Now it is ok: forced to be double
min<double>( 1, 2 );         // Compiler will deduce int, but we want double
template <typename T>
void print_ptr( T* p );
print_ptr<void>( 0 );        // 0 is a valid T* for any T, select manually one
template <typename T>
T min( T lhs, T rhs );
int a = 5, b = 7;
min<int&>(a,b)++;            // Type deduction will drop & by default and call
                             // min<int>(a,b), force the type to be a reference
template <typename C>
typename C::value_type
min_value( typename C::const_iterator begin, typename C::const_iterator end );
std::vector<int> v;
min_value<std::vector<int> >( v.begin(), v.end() ); 
                                 // Argument type is not deducible, there are 
                                 // potentially infinite C that match the constraints
                                 // and the compiler would be forced to instantiate
                                 // all

可能有更多不能推导参数类型的原因,您可以查看标准中的§14.8.2.1,了解从函数调用中推导参数的细节。