在模板功能和自动类型扣除之间进行选择

时间:2015-05-07 21:31:06

标签: c++ templates auto c++14 c++17

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

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

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

有一个TS用于将auto用于功能的参数扣除

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

我虽然使用auto,但是没有办法获得实际类型,例如使用静态成员,但这很好用:

#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;
}    

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

3 个答案:

答案 0 :(得分:5)

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

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

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

所以,为了让代码更接近等价,你真的需要更像这样编写模板:

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

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

答案 1 :(得分: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中已经可用于lambdas的功能,但已被推广到所有功能模板。该功能允许编译器通过检查正文内的不同return语句来查找函数返回的类型。优点是,如果您的模板只有一个返回表达式,则可以避免必须两次输入该表达式,一个用于返回类型,另一个用于实际代码:

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

缺点是编译器需要检查函数体以确定将返回的类型,这必然是post类型替换。因此,具有推导类型的函数的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.),则会导致硬错误。

答案 2 :(得分:0)

在思考之后,我看到使用模板功能的唯一原因是强制几个参数具有相同的类型。使用auto时,每种类型的演绎都是相互独立的。

如果您希望第一个和第三个参数具有相同的类型和第二个参数的泛型类型,您甚至可以混合模板和自动。

/@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d+/

正如Oktalist在评论中所写,你可以使用using语句来获取参数的类型auto。