模板偏序 - 为什么部分推导在这里成功

Template partial ordering - why does partial deduction succeed here(模板偏序 - 为什么部分推导在这里成功)
本文介绍了模板偏序 - 为什么部分推导在这里成功的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

考虑以下简单(就模板问题而言)示例:

Consider the following simple (to the extent that template questions ever are) example:

#include <iostream>

template <typename T>
struct identity;

template <>
struct identity<int> {
    using type = int;
};

template<typename T> void bar(T, T ) { std::cout << "a
"; }
template<typename T> void bar(T, typename identity<T>::type) { std::cout << "b
"; }

int main ()
{
    bar(0, 0);
}

clang 和 gcc 都在那里打印a".根据[temp.deduct.partial]和[temp.func.order]中的规则,要确定偏序,我们需要合成一些唯一的类型.所以我们有两种推论的尝试:

Both clang and gcc print "a" there. According to the rules in [temp.deduct.partial] and [temp.func.order], to determine partial ordering, we need to synthesize some unique types. So we have two attempts at deduction:

+---+-------------------------------+-------------------------------------------+
|   | Parameters                    | Arguments                                 |
+---+-------------------------------+-------------------------------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA                          |
| b | T, T                          | UniqueB, typename identity<UniqueB>::type |
+---+-------------------------------+-------------------------------------------+

对于b"的推导,根据Richard Corden的回答,表达式typename identity::type 被视为一种类型并且不被评估.也就是说,这将被合成为:

For deduction on "b", according to Richard Corden's answer, the expression typename identity<UniqueB>::type is treated as a type and is not evaluated. That is, this will be synthesized as if it were:

+---+-------------------------------+--------------------+
|   | Parameters                    | Arguments          |
+---+-------------------------------+--------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA   |
| b | T, T                          | UniqueB, UniqueB_2 |
+---+-------------------------------+--------------------+

很明显,对b"的推导失败了.这是两种不同的类型,因此您无法将 T 推导出它们.

It's clear that deduction on "b" fails. Those are two different types so you cannot deduce T to both of them.

但是,在我看来,对 A 的推导应该失败.对于第一个参数,您将匹配 T == UniqueA.第二个参数是一个非推导的上下文 - 那么如果 UniqueA 可以转换为 identity,那么推导不会成功吗?后者是替换失败,所以我看不出这个推论如何成功.

However, it seems to me that the deduction on A should fail. For the first argument, you'd match T == UniqueA. The second argument is a non-deduced context - so wouldn't that deduction succeed iff UniqueA were convertible to identity<UniqueA>::type? The latter is a substitution failure, so I don't see how this deduction could succeed either.

在这种情况下,gcc 和 clang 如何以及为什么更喜欢a"重载?

How and why do gcc and clang prefer the "a" overload in this scenario?

推荐答案

正如评论中所讨论的,我相信函数模板偏序算法的几个方面在标准中是不清楚或根本没有规定的,而这个显示在您的示例中.

As discussed in the comments, I believe there are several aspects of the function template partial ordering algorithm that are unclear or not specified at all in the standard, and this shows in your example.

为了让事情变得更有趣,MSVC(我测试了 12 和 14)拒绝了这个模棱两可的调用.我认为标准中没有任何内容可以最终证明哪个编译器是正确的,但我认为我可能对差异的来源有所了解;下面有一个说明.

To make things even more interesting, MSVC (I tested 12 and 14) rejects the call as ambiguous. I don't think there's anything in the standard to conclusively prove which compiler is right, but I think I might have a clue about where the difference comes from; there's a note about that below.

您的问题(以及这个问题)要求我对事情的运作方式进行更多调查.我决定写这个答案并不是因为我认为它具有权威性,而是将我找到的信息组织在一个地方(它不适合评论).我希望它会有用.

Your question (and this one) challenged me to do some more investigation into how things work. I decided to write this answer not because I consider it authoritative, but rather to organize the information I have found in one place (it wouldn't fit in comments). I hope it will be useful.

首先,针对issue 1391.我们在评论和聊天中广泛讨论了它.我认为,虽然它确实提供了一些澄清,但也引入了一些问题.它将 [14.8.2.4p4] 更改为(粗体的新文本):

First, the proposed resolution for issue 1391. We discussed it extensively in comments and chat. I think that, while it does provide some clarification, it also introduces some issues. It changes [14.8.2.4p4] to (new text in bold):

上面从参数模板中指定的每种类型和来自参数模板的对应类型被用作PA.如果一个特定的P 不包含模板参数参与模板参数推导,即 P 不用于确定顺序.

Each type nominated above from the parameter template and the corresponding type from the argument template are used as the types of P and A. If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.

我认为这不是一个好主意,原因有几个:

Not a good idea in my opinion, for several reasons:

  • 如果 P 是非依赖的,则它根本不包含任何模板参数,因此它也不包含任何参与参数推导的参数,这将使粗体语句适用于它.但是,这会使 templatef(T, int)templatef(T, U) 无序,没有意义.这可以说是对措辞的解释问题,但可能会引起混淆.
  • 它混淆了用于确定顺序的概念,这会影响 [14.8.2.4p11].这使得 templatevoid f(T)templatevoid f(typename A<T>::a) unordered(从第一到第二推导成功,因为T没有用于根据新规则用于偏序的类型,所以它可以保持没有价值).目前,我测试过的所有编译器都报告说第二个更专业.
  • 在下面的例子中,它会使 #2#1 更专业:

  • If P is non-dependent, it doesn't contain any template parameters at all, so it doesn't contain any that participate in argument deduction either, which would make the bold statement apply to it. However, that would make template<class T> f(T, int) and template<class T, class U> f(T, U) unordered, which doesn't make sense. This is arguably a matter of interpretation of the wording, but it could cause confusion.
  • It messes with the notion of used to determine the ordering, which affects [14.8.2.4p11]. This makes template<class T> void f(T) and template<class T> void f(typename A<T>::a) unordered (deduction succeeds from first to second, because T is not used in a type used for partial ordering according to the new rule, so it can remain without a value). Currently, all compilers I've tested report the second as more specialized.
  • It would make #2 more specialized than #1 in the following example:

#include <iostream>

template<class T> struct A { using a = T; };

struct D { };
template<class T> struct B { B() = default; B(D) { } };
template<class T> struct C { C() = default; C(D) { } };

template<class T> void f(T, B<T>) { std::cout << "#1
"; } // #1
template<class T> void f(T, C<typename A<T>::a>) { std::cout << "#2
"; } // #2

int main()
{
   f<int>(1, D());
}

(#2 的第二个参数不用于偏序,所以从 #1#2 的推导成功但不反过来).目前,这个电话是模棱两可的,可以说应该仍然如此.

(#2's second parameter is not used for partial ordering, so deduction succeeds from #1 to #2 but not the other way around). Currently, the call is ambiguous, and should arguably remain so.

在查看了 Clang 对偏序算法的实现之后,我认为可以通过以下方式更改标准文本以反映实际发生的情况.

After looking at Clang's implementation of the partial ordering algorithm, here's how I think the standard text could be changed to reflect what actually happens.

保留 [p4] 不变,并在 [p8] 和 [p9] 之间添加以下内容:

Leave [p4] as it is and add the following between [p8] and [p9]:

对于 P/A 对:

  • 如果 P 是非依赖的,当且仅当 PA 是相同类型时,推导才被认为是成功的.
  • 将推导的模板参数代入出现在 P 中的非推导的上下文中不会被执行,并且不会影响推导过程的结果.
  • 如果 P 的所有模板参数的模板参数值都被成功推导,除了那些只出现在非推导上下文中的参数,那么推导被认为是成功的(即使在 中使用了一些参数)P 在该特定 P/A 对的推导过程结束时仍然没有值).
  • If P is non-dependent, deduction is considered successful if and only if P and A are the same type.
  • Substitution of deduced template parameters into the non-deduced contexts appearing in P is not performed and does not affect the outcome of the deduction process.
  • If template argument values are successfully deduced for all template parameters of P except the ones that appear only in non-deduced contexts, then deduction is considered successful (even if some parameters used in P remain without a value at the end of the deduction process for that particular P / A pair).

注意事项:

  • 关于第二个要点:[14.8.2.5p1] 讨论了在替换推导出的值(称之为推导出的 A),兼容A.这可能会导致对部分排序期间实际发生的事情感到困惑;没有进行替换.
  • 在某些情况下,MSVC 似乎没有实现第三个要点.有关详细信息,请参阅下一部分.
  • 第二个和第三个要点也旨在涵盖 P 具有像 A<T, typename U::b> 这样的形式的情况,这些形式没有被涵盖按照第 1391 期的措辞.
  • About the second bullet point: [14.8.2.5p1] talks about finding template argument values that will make P, after substitution of the deduced values (call it the deduced A), compatible with A. This could cause confusion about what actually happens during partial ordering; there's no substitution going on.
  • MSVC doesn't seem to implement the third bullet point in some cases. See the next section for details.
  • The second and third bullet points are intented to also cover cases where P has forms like A<T, typename U::b>, which aren't covered by the wording in issue 1391.

将当前的 [p10] 更改为:

Change the current [p10] to:

函数模板 F 至少和函数模板一样专业G 当且仅当:

Function template F is at least as specialized as function template G if and only if:

  • 对于用于确定排序的每一对类型,F 中的类型至少与 G 中的类型一样特殊,并且,
  • 以转换后的F为参数模板,G为参数模板进行推导时,推导完成后对于所有类型对,类型中使用的所有模板参数来自用于确定排序的 G 有值,而那些所有类型对的值都是一致的.
  • for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G, and,
  • when performing deduction using the transformed F as the argument template and G as the parameter template, after deduction is done for all pairs of types, all template parameters used in the types from G that are used to determine the ordering have values, and those values are consistent across all pairs of types.

F G 更专业,如果 F 至少一样专业GG 至少不像 F 那样专业.

F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.

将整个当前 [p11] 记为笔记.

Make the entire current [p11] a note.

([14.8.2.5p4]的1391分辨率添加的注释也需要调整,[14.8.2.1]可以,[14.8.2.4]不行)

(The note added by the resolution of 1391 to [14.8.2.5p4] needs to be adjusted as well - it's fine for [14.8.2.1], but not for [14.8.2.4].)

对于 MSVC,在某些情况下,P 中的所有模板参数看起来都需要在推导过程中为该特定 P/A 接收值 pair 使得从AP的推导成功.我认为这可能是导致您的示例和其他示例中实现差异的原因,但我至少看到过一种情况,其中上述内容似乎并不适用,因此我不确定该相信什么.

For MSVC, in some cases, it looks like all template parameters in P need to receive values during deduction for that specific P / A pair in order for deduction to succeed from A to P. I think this could be what causes implementation divergence in your example and others, but I've seen at least one case where the above doesn't seem to apply, so I'm not sure what to believe.

上述语句似乎适用的另一个示例:更改 templatevoid bar(T, T) to templatevoid bar(T, U) 在您的示例中交换结果:调用在 Clang 和 GCC 中不明确,但在 MSVC 中解析为 b.

Another example where the statement above does seem to apply: changing template<typename T> void bar(T, T) to template<typename T, typename U> void bar(T, U) in your example swaps results around: the call is ambiguous in Clang and GCC, but resolves to b in MSVC.

没有的一个例子:

#include <iostream>

template<class T> struct A { using a = T; };
template<class, class> struct B { };

template<class T, class U> void f(B<U, T>) { std::cout << "#1
"; }
template<class T, class U> void f(B<U, typename A<T>::a>) { std::cout << "#2
"; }

int main()
{
   f<int>(B<int, int>());
}

这在 Clang 和 GCC 中选择了 #2,正如预期的那样,但是 MSVC 拒绝了这个不明确的调用;不知道为什么.

This selects #2 in Clang and GCC, as expected, but MSVC rejects the call as ambiguous; no idea why.

标准中描述的偏序算法涉及合成一个独特的类型、值或类模板以生成参数.Clang 通过……不合成任何东西来管理它.它只是使用依赖类型的原始形式(如声明的那样)并以两种方式匹配它们.这是有道理的,因为替换合成类型不会添加任何新信息.它不能改变 A 类型的形式,因为通常没有办法知道替代的形式可以解析为哪些具体类型.合成类型未知,这使得它们与模板参数非常相似.

The partial ordering algorithm as described in the standard speaks of synthesizing a unique type, value, or class template in order to generate the arguments. Clang manages that by... not synthesizing anything. It just uses the original forms of the dependent types (as declared) and matches them both ways. This makes sense, as substituting the synthesized types doesn't add any new information. It can't change the forms of the A types, since there's generally no way to tell what concrete types the substituted forms could resolve to. The synthesized types are unknown, which makes them pretty similar to template parameters.

当遇到一个 P 是一个非推导的上下文时,Clang 的模板参数推导算法简单地跳过它,通过为该特定步骤返回成功".这不仅发生在偏序期间,而且发生在所有类型的推导中,不仅发生在函数参数列表的顶层,而且每当遇到复合类型形式的非推导上下文时都会递归.出于某种原因,我第一次看到它时就感到惊讶.仔细想想,这当然是有道理的,而且是符合标准的([...] 不参与类型推导[...] in [14.8.2.5p4]).

When encountering a P that is a non-deduced context, Clang's template argument deduction algorithm simply skips it, by returning "success" for that particular step. This happens not only during partial ordering, but for all types of deductions, and not just at the top level in a function parameter list, but recursively whenever a non-deduced context is encountered in the form of a compound type. For some reason, I found that surprising the first time I saw it. Thinking about it, it does, of course, make sense, and is according to the standard ([...] does not participate in type deduction [...] in [14.8.2.5p4]).

这与 Richard Corden 对 他的回答,但我必须实际查看编译器代码才能理解所有含义(不是他的回答的错,而是我自己的错 - 程序员在代码中思考等等).

This is consistent with Richard Corden's comments to his answer, but I had to actually see the compiler code to understand all the implications (not a fault of his answer, but rather of my own - programmer thinking in code and all that).

我在这个答案中包含了一些有关 Clang 实现的更多信息.

I've included some more information about Clang's implementation in this answer.

这篇关于模板偏序 - 为什么部分推导在这里成功的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

本站部分内容来源互联网,如果有图片或者内容侵犯了您的权益,请联系我们,我们会在确认后第一时间进行删除!

相关文档推荐

How do compilers treat variable length arrays(编译器如何处理变长数组)
Deduce template argument from std::function call signature(从 std::function 调用签名推导出模板参数)
check if member exists using enable_if(使用 enable_if 检查成员是否存在)
Standard Library Containers with additional optional template parameters?(具有附加可选模板参数的标准库容器?)
Uses of a C++ Arithmetic Promotion Header(C++ 算术提升标头的使用)
Parameter pack must be at the end of the parameter list... When and why?(参数包必须位于参数列表的末尾...何时以及为什么?)