模板专用化和明确指定返回类型与自动

Template specialization and explicit specification of return type vs. auto

本文关键字:返回类型 专用      更新时间:2023-10-16

考虑代码:

class Test {
public:
template<int N> auto foo() {}
template<> auto foo<0>() { return 7;  }
template<int N> void bar() {}
template<> int bar<0>() { return 7;  }
};

我已经用不同的编译器(通过编译器资源管理器)测试了代码。

如果Clang 7.0.0foo编译,而bar给出错误:

:8:20:错误:没有与函数模板匹配的函数模板专业化‘bar’

template<> int bar<0>() { return 7;  }
^

:7:26:注意:已忽略候选模板:无法匹配"void"()'对'int()'

template<int N> void bar() {};
^

Visual C++同意(MSVC 19 2017 RTW):

(8):错误C2912:显式专用化'int测试::bar(void)'不是函数模板的专门化

gcc 8.2没有编译任何代码(尽管原因可能是C++17支持中的一个错误:

:5:14:错误:非命名空间范围中的显式专用化"等级测试">

template<> auto foo<0>() { return 7;  };
^

:5:28:错误:模板id'foo<主声明中的"0>"模板

template<> auto foo<0>() { return 7;  };
^

:7:26:错误:模板参数列表太多

template<int N> void bar() {};
^~~

:8:14:错误:非命名空间作用域中的显式专用化"等级测试">

template<> int bar<0>() { return 7;  }
^

:8:20:错误:应为";"会员申报结束时

template<> int bar<0>() { return 7;  }
^~~
;

:8:23:错误:"<"之前应为不合格的id代币

template<> int bar<0>() { return 7;  }
^

这里的正确解释是什么?对于不同的方法专门化,我可以有一个不同的返回类型吗(为什么只使用auto,而不是显式指定它们)?由于我对auto和模板的了解有限,我会说"不"。我不明白为什么使用auto而不是显式命名返回类型,可以为不同的专业化提供不同的返回类型。

然而,这些代码是我在其他地方发现的代码的简化版本,所以我的解释可能是不正确的——在这种情况下,我将感谢解释为什么在使用auto进行专门化时允许不同的返回类型,而显式命名类型似乎是被禁止的

示例代码存在几个不同的问题。

1) GCC未能执行CWG 727(C++17中要求):https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282导致error: explicit specialization in non-namespace scope 'class Test'

2) 如果我们忽略这一点,示例代码可以简化为

template<int N> auto foo() {}
template<> auto foo<0>() { return 7; }
template<int N> void bar() {}
template<> int bar<0>() { return 7; }

其仍然表现出相同的误差。现在,所有编译器都对输出达成一致。他们编译foo,并在bar专用化上出错。

为什么在使用auto进行专业化时允许不同的返回类型

如果专门化还具有auto占位符,则标准允许专门化具有auto返回类型的函数

函数或函数模板的重新声明或特殊化,如果声明的返回类型使用占位符类型,则也应使用该占位符,而不是推导类型

http://eel.is/c++draft/dcl.spec.auto#11

因此,由于这种专业化符合标准(类似于给定的例子),并且在任何允许的地方都没有被明确禁止。

至于bar的错误,标准规定返回类型是函数模板签名的一部分:

⟨函数模板名称、参数类型列表([dcl.fct])、封闭命名空间(如果有)、返回类型、模板头和尾部requires子句([dcl.dell])(如果有的话)

http://eel.is/c++draft/defs.signature.templ

因此,在编译器看来,template<> int bar<0>() { return 7; }template<... N> int bar();模板的特殊化(注意返回类型)。但它以前没有声明(特殊化不能先于声明),所以编译失败!如果您添加template<int N> int bar();,那么它将编译(但如果您尝试调用bar,则会抱怨调用不明确)。

基本上,您不能在专门化中更改函数签名,只能专门化(duh)模板参数(也应该与声明中的参数完全相同,因为它也是签名的一部分)。

我可以在所有中使用显式声明的返回类型与基本模板不同的模板专用化吗

如前所述,您不能更改函数模板签名,这意味着您不能更改返回类型。但是,如果返回类型依赖于模板参数,则可以专门化它!

考虑

template<int N, typename R = void> R bar() {}
template<> int bar<0>() { return 7; }
// bar<0, int> is deduced, see: http://eel.is/c++draft/temp.expl.spec#10

这是允许的,但它的缺点是,当您想调用专业化时,必须编写bar<0, int>:https://godbolt.org/z/4lrL62

这可以通过在原始声明中使类型成为条件类型来解决:https://godbolt.org/z/i2aQ5Z

但一旦专业化的数量增加,维护起来就会很麻烦。

另一个可能更易于维护的选项是返回类似ret_type<N>::type的内容,并将其与bar一起进行专门化。但它仍然不会像使用auto那样干净。