[英]Template parameters of function type with auto return type arguments of previous template parameter types
我有一个带有两个参数的模板:第一个是类型,第二个是 function 指针,其参数的类型是第一个模板参数。 此 MCVE 有效:
void returnsVoid(int x) { }
template <typename T, void (*Func)(T)>
struct foo { void bar(T t) { Func(t); } };
int main(int, char *[]) {
foo<int, returnsVoid> a; // ok
}
但是,当我将第二个模板参数的返回类型更改为auto
时(如相关问题中所述),我收到错误消息:
void returnsVoid(int x) { }
template <typename T, auto (*Func)(T)>
struct foo {
void bar(T t) { Func(t); }
};
int main(int, char *[]) {
foo<int, returnsVoid> a; // error: unable to deduce ‘auto (*)(T)’ from ‘returnsVoid’
// note: mismatched types ‘T’ and ‘int’
}
为什么这不再适用于auto
返回类型?
我在 Ubuntu Linux 机器上使用 g++ 9.3.0。 clang 10 中出现类似错误。
这是 gcc 的错误。 (错误 83417 )
当使用第一个模板参数T
作为 function 参数时,似乎 gcc 未能推断出auto
的类型; 您可以使用std::type_identity
(C++20 起) 将其排除在参与模板参数推导之外。
// workaround for gcc
template <typename T, auto (*Func)(std::type_identity_t<T>)>
struct foo {
void bar(T t) { Func(t); }
};
顺便说一句: Clang 10似乎运行良好。
除非另有说明,否则以下所有标准参考均指N4861(2020 年 3 月布拉格后工作草案/C++20 DIS) 。
跳转到A 解决方法,以缓解本文底部的 GCC 错误部分,以获得一种解决方法,被 GCC 和 Clanng 接受,它仍然依赖于依赖类型的推断,以避免客户端必须为实际类型指定第二个模板参数与“main”模板参数相关联的 function 参数; 即 function 指针使用了关联的非类型模板参数。
当从表达式推导出与以依赖类型声明的非类型模板形参
P
对应的实参的值时,从值的类型推导出P
类型中的模板形参。 [示例:template<long n> struct A { }; template<typename T> struct C; template<typename T, T n> struct C<A<n>> { using Q = T; }; using R = long; using R = C<A<2>>::Q; // OK; T was deduced as long from the // template argument value in the type A<2>
—结束示例]
经历类型推导(从表达式)的非类型模板参数的声明中的任何依赖类型也应从非类型模板参数的关联参数推导。 同样重要的是[temp.deduct.type]/5 ,涵盖非推导上下文,不适用于 function 指针的非类型模板参数中的依赖类型的一般用途; 在 OP 的示例中, T
是一个依赖类型,因此是从非类型(函数指针)模板参数的参数值推导出来的。
当一个给定的,比如说,类型模板参数是从多个来源推导出来的(例如在 OP 的例子中),一个常见的问题是推导产生不同的类型。 例如,如以下博客文章所示:
template<typename T> struct Foo { T t; }; template<typename T> void addToFoo(Foo<T>& foo, T val) { foo.t += val; } int main() { Foo<long> f{42}; addToFoo(f, 13); // error: no matching function for call to 'addToFoo' // note: candidate template ignored: deduced conflicting // types for parameter T (long vs. int). return 0; }
正如@songyuanyao: answer中所显示的那样(也如博客文章中所示),对于多个推导源产生冲突的情况,可以使用类型标识转换特征有意地将给定的模板参数放置在非推导上下文中结果。
但是,OP:s 失败的根本原因不是推导结果冲突(这是一条红鲱鱼),而是 GCC:s 未能正确推导模板参数,从推导另一个模板参数时,前者作为依赖项存在类型。
因此,如果我们将 go 返回到 [temp.deduct.type]/13,对于以下 class 模板和随后的部分特化:
// #1
template <auto>
struct A { static void dispatch() = delete; };
// #2
template <typename T, void (*fun)(T)>
struct A<fun> {
static void dispatch() {
T t{};
fun(t);
}
};
以下:
A<f>::dispatch(); // #3
如果f
是一个 function 具有一个返回void
的单个参数(类型是默认可构造的),则格式正确,例如
void f(int) { std::cout << "void f(int)\n"; }
// -> #3 is well-formed
因为这将匹配#2
处的部分特化,将T
推导出为int
和非类型模板参数(它决定由主模板制定的特化蓝图),在此部分特化中依赖于T
,以void(*)(int)
。
另一方面,如果f
不返回void
,则#3
格式不正确,因为#2
的部分特化不再可行。
void f(int) { std::cout << "int f(int)\n"; }
// -> #3 is ill-formed
这里的关键是:
#2
的部分特化仅适用于与特化的第二个(非类型)模板参数匹配的模板 arguments 到A
,并且T
是从第二个(非类型)模板参数的推导推导出来的,因为T
是第二个模板参数声明中的依赖类型。对于上述两种情况,GCC 和 Clang 都按预期工作。
现在,如果我们考虑与上面的#1
和#2
类似的例子:
// #4
template <auto>
struct B { static void dispatch() = delete; };
// #5
template <typename T, auto (*fun)(T)>
struct B<fun> {
static void dispatch() {
T t{};
fun(t);
}
};
// ... elsewere
// #6
B<f>::dispatch();
与上述相同的论点适用:
f
引用了一个 function (现在限制较少),它有一个参数(类型是默认可构造的),那么#5
的部分特化是可行的,它的第二个非类型模板参数,它包含其第一个类型模板参数作为依赖类型,应用于推导后者。这种情况下的显着区别是非类型模板参数的类型本身经历了[temp.arg.nontype]/1 :
如果模板参数的类型
T
包含占位符类型 ([dcl.spec.auto]) 或推导的 class 类型的占位符 ([dcl.type.class.deduct]),则参数的类型是类型为发明声明中的变量x
推导出来T x = template-argument;
如果模板参数声明([temp.param])不允许推导参数类型,则程序格式错误。
但是 [temp.deduct.type]/13 仍然适用,而且我们可能不是 [temp.deduct.type]/13 实际上是作为P0127R2的一部分添加的(使用auto
声明非类型模板参数),它引入了占位符类型对于 C++17 的非类型模板参数。
因此,核心问题是当声明依赖类型(要推导)的非类型模板参数是function 指针(或者,如链接到 GCC 错误报告中所示,作为指向成员的指针)并且使用占位符类型( auto
)声明非类型模板参数。
@songyuanyao:答案显示了此错误的解决方法,应用于 OP 的示例,只需使依赖类型不依赖,因为可以从其他地方推导出关联的模板参数(即从 OP 示例中的第一个模板参数)。 这对于上面的例子是行不通的,我们在推演非类型模板参数时仅仅依靠依赖类型推导来找到类型模板参数的类型(即前者中的依赖类型)。
对于一个实际的客户端 API,要求客户端明确指定参数的类型给作为另一个参数提供的 function,当前者完全可以从后者推导出来时,可以说是冗余设计,并且在提供时会导致客户端混淆这两个模板参数的 arguments 冲突。 因此,可以说我们希望依靠上面显示的部分专业化技术,但如这些答案所示,GCC 在这方面失败了,以防我们希望客户端不限于特定的返回类型。
但是,我们可以通过使用与上述相同的方法来解决这个问题,仍然依赖 [temp.deduct.type]/13,但在部分特化中使用附加的类型模板参数(用于返回类型):
#include <iostream>
template <auto>
struct C { static void dispatch() = delete; };
template <typename T, typename Return, Return (*fun)(T)>
struct C<fun> {
static void dispatch() {
T t{};
fun(t);
}
};
void f(int) { std::cout << "void f(int)\n"; }
int g(int) { std::cout << "int f(int)\n"; return 0; }
int main() {
C<f>::dispatch();
C<g>::dispatch();
}
客户端不需要担心部分特化的附加模板参数,因为它们完全可以通过特化的第三个非类型模板参数推导出来,它们依赖于其声明。 最后一个示例被 GCC 和 Clang 接受。
这是CWG2476 :[dcl.spec.auto]/2 要求仅在 function 声明符声明 function 的情况下,将auto
用作(部分)没有尾随返回类型的返回类型。 模板参数声明不这样做,所以它是无效的。 有人可能会争辩说 /5 无论如何都允许它在模板参数的decl-specifier-seq中使用,但这没有意义,因为它会剥夺以下含义:
template<auto g() -> int>
int f() {return g();}
也就是说,扣除规则在这里会起作用,所以这是措辞中的一个缺陷,可能会通过说返回类型扣除只是不会发生在auto
出于其他原因而允许的情况下发生。
正如@songyuanyao 所述,这似乎是一个 gcc 错误。
对于 <= c++17 一种解决方案是将类型 T 从 function 签名移动到非推断上下文:
template <typename T>
struct identity { using type = T; };
接着
template <typename T, auto (*Func)(typename identity<T>::type)>
struct foo {
void bar(T t) { Func(t); }
};
另一种方法是更改设计并移至成员 function 模板。 自动类型扣除在这种情况下有效:
void returnsVoid(int) { }
template <typename T>
struct foo {
template <auto (*Func)(T)>
void bar(T t) { Func(t); }
};
int main(int, char *[]) {
foo<int> a;
a.bar<returnsVoid>(3);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.