简体   繁体   English

function 类型与自动返回类型的模板参数 arguments 以前的模板参数类型

[英]Template parameters of function type with auto return type arguments of previous template parameter types

I have a template with two parameters: the first is a type, and the second is a function pointer with an argument whose type is the first template parameter.我有一个带有两个参数的模板:第一个是类型,第二个是 function 指针,其参数的类型是第一个模板参数。 This MCVE works:此 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
}

However, when I change the return type of the second template parameter to auto (as explained in this related question ), I get an error:但是,当我将第二个模板参数的返回类型更改为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’
}

Why does this no longer work with an auto return type?为什么这不再适用于auto返回类型?

I'm using g++ 9.3.0 on an Ubuntu Linux machine.我在 Ubuntu Linux 机器上使用 g++ 9.3.0。 A similar error occurs in clang 10. clang 10 中出现类似错误。

This is gcc's bug.这是 gcc 的错误。 ( Bug 83417 ) 错误 83417

It seems gcc failing deducing the type of auto when using the 1st template parameter T as function parameter;当使用第一个模板参数T作为 function 参数时,似乎 gcc 未能推断出auto的类型; you can use std::type_identity (since C++20) to exclude it from participating in template argument deduction .您可以使用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); }
};

LIVE居住

BTW: Clang 10 seems working well.顺便说一句: Clang 10似乎运行良好。

All standard references below refer, unless noted otherwise, to N4861 (March 2020 post-Prague working draft/C++20 DIS) .除非另有说明,否则以下所有标准参考均指N4861(2020 年 3 月布拉格后工作草案/C++20 DIS)


TL;DR; TL;博士;

Jump to the A workaround to mitigate the GCC bug section in the bottom of this post for a workaround, accepted by GCC and Clanng, which still relies of deduction of dependent types to avoid a client having to specify a second template argument for the actual type of the function parameter associated with the "main" template argument;跳转到A 解决方法,以缓解本文底部的 GCC 错误部分,以获得一种解决方法,被 GCC 和 Clanng 接受,它仍然依赖于依赖类型的推断,以避免客户端必须为实际类型指定第二个模板参数与“main”模板参数相关联的 function 参数; namely the function pointer used associated non-type template parameter.即 function 指针使用了关联的非类型模板参数。

Standardese标准话

As per [temp.deduct.type]/13 :根据[temp.deduct.type]/13

When the value of the argument corresponding to a non-type template parameter P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value.当从表达式推导出与以依赖类型声明的非类型模板形参P对应的实参的值时,从值的类型推导出P类型中的模板形参。 [ Example: [示例:

 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>

end example ] 结束示例]

any dependent types in the declaration of a non-type template parameter that undergoes type deduction (from an expression) shall also be deduced from the associated argument for the non-type template parameter.经历类型推导(从表达式)的非类型模板参数的声明中的任何依赖类型也应从非类型模板参数的关联参数推导。 It is also essential that [temp.deduct.type]/5 , covering the non-deduced contexts , does not apply for general uses of dependent types within non-type template parameters that are function pointers;同样重要的是[temp.deduct.type]/5 ,涵盖非推导上下文,不适用于 function 指针的非类型模板参数中的依赖类型的一般用途; meaning in the OP's example, T is a dependent type and is thus deduced from the value of the argument to the non-type (function pointer) template parameter.在 OP 的示例中, T是一个依赖类型,因此是从非类型(函数指针)模板参数的参数值推导出来的。

A common problem when a given, say, type template parameter is deduced from more than one source (eg as in the example of OP), is that deduction yields different types;当一个给定的,比如说,类型模板参数是从多个来源推导出来的(例如在 OP 的例子中),一个常见的问题是推导产生不同的类型。 eg as showing in the following blog post :例如,如以下博客文章所示

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

As has been shown in @songyuanyao: answer (and as is shown also in the blog post), a type identity transformation trait can be used to intentionally place a given template parameter in a non-deduced context for cases where several deduction sources yields conflicting results.正如@songyuanyao: answer中所显示的那样(也如博客文章中所示),对于多个推导源产生冲突的情况,可以使用类型标识转换特征有意地将给定的模板参数放置在非推导上下文中结果。

However, the root cause of OP:s failure is not conflicting deduction results (this is a red herring), but rather GCC:s failure to correctly deduce template parameter from when another template parameter is deduced, where the former is present as a dependent type.但是,OP:s 失败的根本原因不是推导结果冲突(这是一条红鲱鱼),而是 GCC:s 未能正确推导模板参数,从推导另一个模板参数时,前者作为依赖项存在类型。

Thus, if we go back to [temp.deduct.type]/13, for the following class template and subsequent partial specialization:因此,如果我们将 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);
    }
};

the following:以下:

A<f>::dispatch(); // #3

is well-formed if f is a function with a single argument (of a type that is default-constructible) which returns void , eg如果f是一个 function 具有一个返回void的单个参数(类型是默认可构造的),则格式正确,例如

void f(int) { std::cout << "void f(int)\n"; }
// -> #3 is well-formed

as this will match the partial specialization at #2 , deducing T to int and the non-type template parameter (which decides the specialization blueprinted by the primary template), which is dependent on T in this partial specialization, to void(*)(int) .因为这将匹配#2处的部分特化,将T推导出为int和非类型模板参数(它决定由主模板制定的特化蓝图),在此部分特化中依赖于T ,以void(*)(int)

On the other hand, #3 is ill-formed if f does not return void , as the partial specialization at #2 is no longer viable.另一方面,如果f不返回void ,则#3格式不正确,因为#2的部分特化不再可行。

void f(int) { std::cout << "int f(int)\n"; }
// -> #3 is ill-formed

The key here is that:这里的关键是:

  • the partial specialization at #2 applies only for template arguments to A which match the second (non-type) template parameter of the specialization, and #2的部分特化仅适用于与特化的第二个(非类型)模板参数匹配的模板 arguments 到A ,并且
  • the first template parameter of the specialization, T , is deduced from the deduction of the second (non-type) template parameter, as T is a dependent type in the declaration of the second template parameter.特化的第一个模板参数T是从第二个(非类型)模板参数的推导推导出来的,因为T是第二个模板参数声明中的依赖类型。

Both GCC and Clang works as expected for the two cases above.对于上述两种情况,GCC 和 Clang 都按预期工作。

Now, if we consider the similar example as to that of #1 and #2 above:现在,如果我们考虑与上面的#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();

the same argument as above applies:与上述相同的论点适用:

  • if template argument f refers to a function (now with less restrictions) that has a single argument (of a type that is default-constructible), then the partial specialization at #5 is viable, and its second non-type template parameter, which contains its first type template parameter as a dependent type, shall be used to deduce the latter.如果模板参数f引用了一个 function (现在限制较少),它有一个参数(类型是默认可构造的),那么#5的部分特化是可行的,它的第二个非类型模板参数,它包含其第一个类型模板参数作为依赖类型,应用于推导后者。

The significant difference in this case is that the type of the non-type template parameter itself undergoes [temp.arg.nontype]/1 :这种情况下的显着区别是非类型模板参数的类型本身经历了[temp.arg.nontype]/1

If the type T of a template-parameter contains a placeholder type ([dcl.spec.auto]) or a placeholder for a deduced class type ([dcl.type.class.deduct]), the type of the parameter is the type deduced for the variable x in the invented declaration如果模板参数的类型T包含占位符类型 ([dcl.spec.auto]) 或推导的 class 类型的占位符 ([dcl.type.class.deduct]),则参数的类型是类型为发明声明中的变量x推导出来

T x = template-argument;

If a deduced parameter type is not permitted for a template-parameter declaration ([temp.param]), the program is ill-formed.如果模板参数声明([temp.param])不允许推导参数类型,则程序格式错误。

but [temp.deduct.type]/13 still applies the same, and we may not that [temp.deduct.type]/13 was actually added as part of P0127R2 ( Declaring non-type template parameters with auto ) which introduced placeholder types for non-type template parameters for C++17.但是 [temp.deduct.type]/13 仍然适用,而且我们可能不是 [temp.deduct.type]/13 实际上是作为P0127R2的一部分添加的(使用auto声明非类型模板参数),它引入了占位符类型对于 C++17 的非类型模板参数。

Thus, the core issue is that GCC fails to perform dependent type deduction (as specified in [temp.deduct.type]/13) when the non-type template parameter in whose declaration the dependent type (to be deduced) is present is a function pointer (or, as shown in the linked to GCC bug report , as pointer to member) AND the non-type template parameter is declared with a placeholder type ( auto ).因此,核心问题是当声明依赖类型(要推导)的非类型模板参数是function 指针(或者,如链接到 GCC 错误报告中所示,作为指向成员的指针)并且使用占位符类型( auto )声明非类型模板参数。

@songyuanyao: answer shows a workaround to this bug, applied to OP's example, simply making the dependent type non-dependent, as the associated template parameter can be deduced from elsewhere (namely from the first template argument in OP's example). @songyuanyao:答案显示了此错误的解决方法,应用于 OP 的示例,只需使依赖类型不依赖,因为可以从其他地方推导出关联的模板参数(即从 OP 示例中的第一个模板参数)。 This would not work for the examples above, where we rely solely on the dependent type deduction in the deduction of the non-type template parameter to find the type of the type template parameter (which is the dependent type in the former).这对于上面的例子是行不通的,我们在推演非类型模板参数时仅仅依靠依赖类型推导来找到类型模板参数的类型(即前者中的依赖类型)。

For an actual client API, requiring the client to explicitly specify the type of the argument to the function which is provided as another argument, when the former is entirely deducible from the latter, is arguably redundant design, and opens up for client confusion when providing conflicting arguments for these two template parameters.对于一个实际的客户端 API,要求客户端明确指定参数的类型给作为另一个参数提供的 function,当前者完全可以从后者推导出来时,可以说是冗余设计,并且在提供时会导致客户端混淆这两个模板参数的 arguments 冲突。 Thus, we'd arguably like to fall back on the partial specialization technique shown above, but as shown in these answers, GCC fails us in this regard in case we'd like the client to not be restricted to a specific return type.因此,可以说我们希望依靠上面显示的部分专业化技术,但如这些答案所示,GCC 在这方面失败了,以防我们希望客户端不限于特定的返回类型。

A workaround to mitigate the GCC bug缓解 GCC 错误的解决方法

We can work our way around this, however, by using the same approach as above, still relying on [temp.deduct.type]/13, but by using an additional type template parameter (for the return type) in the partial specialization:但是,我们可以通过使用与上述相同的方法来解决这个问题,仍然依赖 [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();
}

The client will not need to worry about the additional template parameters of the partial specialization, as they entirely deducible via the third non-type template parameter of the specialization, in whose declaration they are dependent.客户端不需要担心部分特化的附加模板参数,因为它们完全可以通过特化的第三个非类型模板参数推导出来,它们依赖于其声明。 This final example is accepted by both GCC and Clang.最后一个示例被 GCC 和 Clang 接受。

This is CWG2476 : [dcl.spec.auto]/2 requires that auto used as (part of) a return type without a trailing-return-type appear only where the function declarator declares a function.这是CWG2476 :[dcl.spec.auto]/2 要求仅在 function 声明符声明 function 的情况下,将auto用作(部分)没有尾随返回类型的返回类型 The template parameter declaration doesn't do that, so it’s invalid .模板参数声明不这样做,所以它是无效的。 One could argue that /5 allows it in a template parameter’s decl-specifier-seq regardless, but that doesn't make sense because it would deprive the following of meaning:有人可能会争辩说 /5 无论如何都允许它在模板参数的decl-specifier-seq中使用,但这没有意义,因为它会剥夺以下含义:

template<auto g() -> int>
int f() {return g();}

That said, the deduction rules would work here, so this is a defect in the wording that will probably be fixed by saying that return type deduction just doesn't happen in the cases where auto is allowed for other reasons.也就是说,扣除规则在这里会起作用,所以这是措辞中的一个缺陷,可能会通过说返回类型扣除只是不会发生在auto出于其他原因而允许的情况下发生。

As already stated by @songyuanyao this seems to be a gcc bug.正如@songyuanyao 所述,这似乎是一个 gcc 错误。

For <= c++17 One solution is to move type T form function signature to non-deduced context:对于 <= c++17 一种解决方案是将类型 T 从 function 签名移动到非推断上下文:

template <typename T>
struct identity { using type = T; };

and then接着

template <typename T, auto (*Func)(typename identity<T>::type)>
struct foo {
    void bar(T t) { Func(t); }
};

Another approach is to change design and to move to a member function template.另一种方法是更改设计并移至成员 function 模板。 The auto type deduction works in this case:自动类型扣除在这种情况下有效:

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

Live居住

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM