3

我有一个关于模板函数与函数的自动类型推导的一般性问题。

多年来,我们已经能够编写模板函数:

template <class T> T add(T a,T b){
    return a+b;
}

函数的参数推导有一个使用auto的TS

auto add(auto a,auto b){
    return a+b;
}

我虽然使用自动,但无法获取实际类型,例如使用静态成员,但这很好用:

#include <iostream>

struct foo
{
    static void bar(){std::cout<<"bar"<<std::endl;}
    static int i ;
};
int foo::i{0};
void t(auto f){
    decltype(f)::bar();
    std::cout<<    decltype(f)::i<<std::endl;
}
int main(int argc, char *argv[])
{
    t(foo());
    return 0;
}    

那么有什么理由选择一个而不是另一个?

4

3 回答 3

5

这个特定代码的明显原因是它们根本没有相同的语义。

特别是,如果您传递不同类型的参数,则使用的版本auto将为每个独立推导一个类型,然后根据这些推导结果的类型。

相反,对于模板版本,您只指定了一种类型,因此参数必须都是相同的类型(并且结果将相同)。

因此,要使代码更接近等效,您确实需要将模板编写得更像:

template <class T, class U> 
auto add(T a, U b) -> decltype(a+b) {
    return a+b;
}

这显然也是可能的,但是为支持使用的论点增加了更多的力量auto

于 2015-05-07T21:58:40.563 回答
3

在您的代码中, 有两种不同的用法auto,一种在参数中,另一种在返回类型中。在参数的情况下,每次使用都会auto引入一个唯一的模板类型参数,所以正如 Jerry 提到的那样,它相当于:

// 1
template <typename A, typename B>
auto add(A a, B b) {
    return a + b;
}

在这种情况下,如果您对不同类型的参数有任何约束(必须相同,可以是一个,但会有其他),那么显式模板语法提供了更好的选择。如果您想在参数上使用 SFINAE(通过额外的模板参数),这是特别的:

// 2
template <typename A, typename B, 
          typename _1 = typename A::iterator,  // A has nested iterator type
          typename _2 = typename B::iterator>  // So does B
auto f(A a, B b);

请注意,我有意避免在返回类型上使用 SFINAE,因为它会以某种方式干扰您对 的其他使用auto,但这可能是另一种选择:

// 3
auto f(auto a, auto b) 
  ->     typename enable_if<has_nested_iterator<decltype(a)>::value
                         && has_nested_iterator<decltype(b)>::value, 
                           [return type] >::type;

但是正如您所看到的,它变得更加复杂,因为您需要使用尾随返回类型并通过decltype.

您的示例中的第二种用法与auto此完全不同,它在返回类型中具有推导的返回类型。推导的返回类型是 C++11 中已经为 lambda 提供的功能,但已推广到所有函数模板。该功能允许编译器通过检查return主体内的不同语句来查找函数返回的类型。优点是,如果您的模板只有一个返回表达式,您可以避免输入该表达式两次,一次用于返回类型,另一次用于实际代码:

// 4
auto add(auto a, auto b) -> decltype(a + b) {  // 'a + b' here
   return a + b;                               // 'a + b' also here
}

缺点是编译器需要检查函数体以确定将返回的类型,这必然是类型替换后的。因此,具有推导类型的函数的 return 语句不能在 SFINAE 表达式中使用,这可能会使函数用户的生活变得复杂:

// 5
auto doAdd(auto a, auto b) 
  -> typename enable_if<is_integral<decltype(add(a,b))>>::type
{
   return add(a,b);
}

SFINAE不会doAdd从重载解析集中移除上述重载,如果您将其称为doAdd(1, 1.).

于 2015-05-07T23:09:49.507 回答
0

After thinking, the only reason I see for using template function is to enforce several parameters to have the same type. While with auto, each type deduction is independent from each other.

You can even mix template and auto if you want the first and third parameter to have the same type and generic type for the second.

template <class T> void barb(T a,auto b,T c){}
int main(int argc, char *argv[])
{
    barb(5," ",5); //ok 
    barb(5," ",5.6); //fails
    return 0;
} 

As Oktalist wrote in the comment, you can use a using statement to get the parameter's type auto.

于 2015-05-07T22:01:18.460 回答