具有未推断上下文的函数模板的部分排序

发布于 2024-07-27 18:49:57 字数 3022 浏览 11 评论 0原文

在阅读另一个问题时,我遇到了部分排序的问题,我将其简化为以下测试用例

template<typename T>
struct Const { typedef void type; };

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

int main() {
  // GCC chokes on f(0, 0) (not being able to match against T1)
  void *p = 0;
  f(0, p);
}

对于这两个函数模板,进入重载解析的专业化的函数类型是 void(int, void*)。 但部分排序(根据 comeau 和 GCC)现在表明第二个模板更加专业。 但为什么?

让我进行部分排序并显示我有疑问的地方。 Q 可能是一种独特的组合类型,用于根据 14.5.5.2 确定部分排序。

  • T1 的转换参数列表(插入 Q):(Q, typename Const::type*)。 参数的类型为 AT = (Q, void*)
  • T2 的转换参数列表(插入 Q):BT = (Q, void*),这也是参数的类型。
  • T1 的未转换参数列表:(T, typename Const::type*)
  • T2 的未转换参数列表>: (T, void*)

由于 C++03 未充分指定这一点,我确实使用了我在几个缺陷报告中读到的意图。 上述 T1 的转换参数列表(我称为 AT)用作 14.8.2.1 的参数列表“推导模板参数来自函数调用”

14.8.2.1 不再需要转换 ATBT 本身(例如,删除引用声明符等),并直接进入 14.8.2.4,它独立地对每个 A / P 对进行类型推导:

  • AT 针对 T2{ (Q, T), (void*, void*) }T是这里唯一的模板参数,它会发现T一定是Q。 对于 ATT2 来说,类型推导很容易成功。

  • BT 对阵 T1{ (Q, T), (void*, typename Const::type*) }。 它会发现T在这里也是Qtypename Const::type* 是一个未推导的上下文,因此它不会用于推导任何内容。


这是我的第一个问题:现在会使用为第一个参数推导的 T 值吗? 如果答案是否定的,那么第一个模板更加专业。 事实并非如此,因为GCC和Comeau都说第二个模板更专业,我不相信他们错了。 因此我们假设“是”,并将 void* 插入到 T 中。 该段落 (14.8.2.4) 表示“对每一对独立进行演绎,然后将结果组合起来” 以及“在某些情况下,然而, value 不参与类型推导,而是使用在其他地方推导或显式指定的模板参数的值。” 这听起来也像“是”。

因此,对于每个 A / P 对,推论也成功。 现在,每个模板至少与另一个模板一样专业,因为推导也不依赖于任何隐式转换,并且在两个方向上都成功了。 因此,该调用应该是不明确的。

所以我的第二个问题:现在,为什么实现说第二个模板更专业? 我忽略了哪一点?


编辑:我测试了显式专业化和实例化,并且在最近的 GCC 版本(4.4)中都告诉我,对专业化的引用不明确,而旧版本的 GCC (4.1) 不会引起歧义错误。 这表明最近的 GCC 版本对函数模板的部分排序不一致。

template<typename T>
struct Const { typedef void type; };

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

template<> void f(int, void*) { }
  // main.cpp:11: error: ambiguous template specialization 
  // 'f<>' for 'void f(int, void*)'

While reading another question, i came to a problem with partial ordering, which i cut down to the following test-case

template<typename T>
struct Const { typedef void type; };

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

int main() {
  // GCC chokes on f(0, 0) (not being able to match against T1)
  void *p = 0;
  f(0, p);
}

For both function templates, the function type of the specialization that enters overload resolution is void(int, void*). But partial ordering (according to comeau and GCC) now says that the second template is more specialized. But why?

Let me go through partial ordering and show where i have questions. May Q be an unique made-up type used for determining partial ordering according to 14.5.5.2.

  • Transformed parameter-list for T1 (Q inserted): (Q, typename Const<Q>::type*). The types of the arguments are AT = (Q, void*)
  • Transformed parameter-list for T2 (Q inserted): BT = (Q, void*), which are also the types of the arguments.
  • Non-transformed parameter-list for T1: (T, typename Const<T>::type*)
  • Non-transformed parameter-list for T2: (T, void*)

Since C++03 under-specifies this, i did use the intention that i read about in several defect reports. The above transformed parameter list for T1 (called AT by me) is used as argument list for 14.8.2.1 "Deducing template arguments from a function call".

14.8.2.1 does not need to transform AT or BT itself anymore (like, removing reference declarators, etc), and goes straight to 14.8.2.4, which independently for each A / P pair does type deduction:

  • AT against T2: { (Q, T), (void*, void*) }. T is the only template parameter here, and it will find that T must be Q. Type deduction succeeds trivially for AT against T2.

  • BT against T1: { (Q, T), (void*, typename Const<T>::type*) }. It will find that T is Q, too here. typename Const<T>::type* is an un-deduced context, and so it won't be used to deduce anything.


Here is my first question: Will this now use the value of T deduced for the first parameter? If the answer is no, then the first template is more specialized. This can't be the case, because both GCC and Comeau say that the second template is more specialized, and i don't believe they are wrong. So we assume "yes", and insert void* into T. The paragraph (14.8.2.4) says "Deduction is done independently for each pair and the results are then combined" and also "In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified." This sounds like "yes" too.

Deduction therefore succeeds too, for every A / P pair. Now, each template is at least as specialized as the other, because deduction didn't also rely on any implicit conversions and succeeded in both directions. As a result, the call should be ambiguous.

So my second question: Now, why do the implementations say that the second template is more specialized? What point did i overlook?


Edit: I tested explicit specialization and instantiation, and both, in recent GCC versions (4.4) tell me that the reference to the specialization is ambiguous, while an older version of GCC (4.1) doesn't rise that ambiguity error. This suggests that recent GCC versions have inconsistent partial ordering for function templates.

template<typename T>
struct Const { typedef void type; };

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

template<> void f(int, void*) { }
  // main.cpp:11: error: ambiguous template specialization 
  // 'f<>' for 'void f(int, void*)'

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

友谊不毕业 2024-08-03 18:49:57

这是我的尝试。 我同意 Charles Bailey 的说法不正确步骤是从 Const::Type*void*

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

我们要采取的步骤是:

14.5.5.2/2

给定两个重载函数模板,可以通过依次转换每个模板并使用参数推导(14.8.2)将其与另一个进行比较来确定一个是否比另一个更专业。

14.5.5.2/3-b1

对于每个类型模板参数,合成一个唯一的类型,并将其替换为函数参数列表中每次出现的该参数,或者替换返回类型中的模板转换函数。

在我看来,类型的合成如下:

(Q, Const<Q>::Type*)    // Q1
(Q, void*)              // Q2

我没有看到任何要求 T1 的第二个合成参数为 void* 的措辞。 我也不知道在其他情况下有任何先例。 类型 Const::Type* 是 C++ 类型系统中完全有效的类型。

现在我们执行推导步骤:

Q2 到 T1

我们尝试推导 T1 的模板参数,因此我们有:

  • 参数 1:T 被推导为 Q
  • 参数 2:非推导上下文

即使参数 2 是非推导上下文,推导仍然成功,因为我们有 T 的值。

Q1 到 T2

推导 T2 的模板参数,我们有:

  • 参数 1:T 推导为 Q
  • 参数 2:void*Const::Type*< 不匹配/code>所以推演失败。

恕我直言,这就是该标准让我们失望的地方。 该参数是不依赖的,所以不太清楚应该发生什么,但是,我的经验(基于对 14.8.2.1/3 的眯眼阅读)是,即使参数类型 P 不依赖,参数类型 A 也应该匹配它。

T1 的综合参数可用于特化 T2,但反之则不然。 因此,T2 比 T1 更专业,因此也是最好的功能。


更新1:

只是为了掩盖关于Const::type无效的观点。 考虑以下示例:

template<typename T>
struct Const;

template<typename T>
void f(T, typename Const<T>::type*) // T1
{ typedef typename T::TYPE1 TYPE; }

template<typename T>
void f(T, void*)                    // T2
{ typedef typename T::TYPE2 TYPE ; }

template<>
struct Const <int>
{
  typedef void type;
};

template<>
struct Const <long>
{
  typedef long type;
};

void bar ()
{
  void * p = 0;
  f (0, p);
}

在上面,当我们执行通常的重载解析规则时,使用 Const::type,但当我们执行部分重载规则时,则不使用。 为 Const::type 选择任意特化是不正确的。 它可能不直观,但编译器很高兴拥有 Const::type* 形式的合成类型并在类型推导期间使用它。


UPDATE 2

template <typename T, int I>
class Const
{
public:
  typedef typename Const<T, I-1>::type type;
};

template <typename T>
class Const <T, 0>
{
public:
  typedef void type;
};

template<typename T, int I>
void f(T (&)[I], typename Const<T, I>::type*)     // T1
{ typedef typename T::TYPE1 TYPE; }

template<typename T, int I>
void f(T (&)[I], void*)                           // T2
{ typedef typename T::TYPE2 TYPE ; }


void bar ()
{
  int array[10];
  void * p = 0;
  f (array, p);
}

当使用某个值 I 实例化 Const 模板时,它会递归实例化自身,直到 I 达到 0。这是选择部分特化 Const 时的情况。 如果我们有一个编译器为函数的参数合成一些实际类型,那么编译器将为数组索引选择什么值? 说10个? 好吧,这对于上面的例子来说是没问题的,但是它与部分特化 Const 不匹配,至少在概念上,这会导致无限数量的递归实例化首要的。 无论它选择什么值,我们都可以将结束条件修改为该值 + 1,然后我们就会在部分排序算法中出现无限循环。

我不明白部分排序算法如何正确实例化 Const 来查找 type 的真正含义。

Here's my go at this. I agree with Charles Bailey that the incorrect step is to go from Const<Q>::Type* to void*

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

The steps we want to take are:

14.5.5.2/2

Given two overloaded function templates, whether one is more specialized than another can be determined by transforming each template in turn and using argument deduction (14.8.2) to compare it to the other.

14.5.5.2/3-b1

For each type template parameter, synthesize a unique type and substitute that for each occurrence of that parameter in the function parameter list, or for a template conversion function, in the return type.

In my opinion, the types are synthesized as follows:

(Q, Const<Q>::Type*)    // Q1
(Q, void*)              // Q2

I don't see any wording that requires that the second synthesized parameter of T1 be void*. I don't know of any precedent for that in other contexts either. The type Const<Q>::Type* is perfectly valid type within the C++ type system.

So now we perform the deduction steps:

Q2 to T1

We try to deduce the template parameters for T1 so we have:

  • Parameter 1: T is deduced to be Q
  • Parameter 2: Nondeduced context

Even though parameter 2 is a non deduced context, deduction has still succeeded because we have a value for T.

Q1 to T2

Deducing the template parameters for T2 we have:

  • Parameter 1: T is deduced to be Q
  • Parameter 2: void* does not match Const<Q>::Type* so deduction failure.

IMHO, here's where the standard lets us down. The parameter is not dependent so it's not really clear what should happen, however, my experience (based on a squinted read of 14.8.2.1/3) is that even where the parameter type P is not dependent, then the argument type A should match it.

The synthesized arguments of T1 can be used to specialize T2, but not vice versa. T2 is therefore more specialized than T1 and so is the best function.


UPDATE 1:

Just to cover the poing about Const<Q>::type being void. Consider the following example:

template<typename T>
struct Const;

template<typename T>
void f(T, typename Const<T>::type*) // T1
{ typedef typename T::TYPE1 TYPE; }

template<typename T>
void f(T, void*)                    // T2
{ typedef typename T::TYPE2 TYPE ; }

template<>
struct Const <int>
{
  typedef void type;
};

template<>
struct Const <long>
{
  typedef long type;
};

void bar ()
{
  void * p = 0;
  f (0, p);
}

In the above, Const<int>::type is used when we're performing the usual overload resolution rules, but not when we get to the partial overloading rules. It would not be correct to choose an arbitrary specialization for Const<Q>::type. It may not be intuitive, but the compiler is quite happy to have a synthasized type of the form Const<Q>::type* and to use it during type deduction.


UPDATE 2

template <typename T, int I>
class Const
{
public:
  typedef typename Const<T, I-1>::type type;
};

template <typename T>
class Const <T, 0>
{
public:
  typedef void type;
};

template<typename T, int I>
void f(T (&)[I], typename Const<T, I>::type*)     // T1
{ typedef typename T::TYPE1 TYPE; }

template<typename T, int I>
void f(T (&)[I], void*)                           // T2
{ typedef typename T::TYPE2 TYPE ; }


void bar ()
{
  int array[10];
  void * p = 0;
  f (array, p);
}

When the Const template is instantiated with some value I, it recursively instantiates itself until I reaches 0. This is when the partial specialization Const<T,0> is selected. If we have a compiler which synthesizes some real type for the parameters of the function, then what value will the compiler choose for the array index? Say 10? Well, this would be fine for the above example but it wouldn't match the partial specialization Const<T, 10 + 1> which, conceptually at least, would result in an infinite number of recursive instantiations of the primary. Whatever value that it selected we could modify the end condition to be that value + 1, and then we'd have an infinite loop in the partial ordering algorithm.

I do not see how the partial ordering algorithm could correctly instantiate Const to find what type really is.

月野兔 2024-08-03 18:49:57

编辑:在研究了部分排序算法的Clang实现(由Doug Gregor)之后,我开始同意其余的发帖者认为,原始示例并非“有意”含糊不清 - 尽管标准对于在这种情况下应该发生的情况并不那么清楚。 我编辑了这篇文章以表明我修改后的想法(为了我自己的利益和参考)。 特别是,Clang 的算法澄清了“typename Const::type”在部分排序步骤期间不会转换为“void”,并且每个 A/P 对都是相互独立推导的。

最初我想知道为什么以下内容被认为是不明确的:

        template<class T> void f(T,T*);  // 1

        template<class T> void f(T, int*); // 2

        f(0, (int*)0); // ambiguous

(上面是不明确的,因为不能从 f2(T,int*) 推导出 f1(U1,U1*),而换句话说,不能推导出 f2(U2 ,int*) 来自 f1(T,T*)。两者都不是更专业化。)

但以下内容不会含糊不清:(

        template<class T> struct X { typedef int type; };
        template<class T> void f(T, typename X<T>::type*); // 3
        template<class T> void f(T, int*); // 2

人们可能期望它含糊不清的原因是如果发生以下情况:< br>
- f3(U1,X::type*) -> f3(U1, int*) ==> f2(T,int*)(推导可以,T=U1)
- f2(U2,int*) ==> f3(T, X::type*) (推导可以,T=U2 使得 X::type* -> int*)
如果这是真的,那么两者都不会比另一个更专业。)

在研究了 Clang 的部分排序算法之后,很明显他们将上面的“3”视为好像:

template<class T, class S> void f(T, S*); // 4

因此根据“typename X”扣除一些唯一的“U”: :type' 将成功 -

  • f3(U1,X::type*) 被视为 f3(U1, U2*) ==> f2(T,int*) (推导不行)
  • f2(U2,int*) ==> f3(T,S* [[X::type*]]) (推论可以,T=U2, S=int)

因此'2'显然比'3'更专业。

Edit: After studying Clang's implementation (by Doug Gregor) of their partial ordering algorithm, I have come to agree with the rest of the posters that the original example is not 'intended' to be ambiguous - even though the standard is not as clear as it could be about what should happen in such situations. I have edited this post to indicate my revised thoughts (for my own benefit & reference). In particular Clang's algorithm clarified that 'typename Const<T>::type' is not translated into 'void' during the partial ordering step - and that each A/P pair is deduced independent of each other.

Initially I wondered why the following was considered ambiguous:

        template<class T> void f(T,T*);  // 1

        template<class T> void f(T, int*); // 2

        f(0, (int*)0); // ambiguous

(The above is ambiguous because one cannot deduce f1(U1,U1*) from f2(T,int*), and going the other way, one cannot deduce f2(U2,int*) from f1(T,T*). Neither is more specialized.)

but the following would not be ambiguous:

        template<class T> struct X { typedef int type; };
        template<class T> void f(T, typename X<T>::type*); // 3
        template<class T> void f(T, int*); // 2

(The reason one could expect it to be ambiguous is if the following were to happen:
- f3(U1,X<U1>::type*) -> f3(U1, int*) ==> f2(T,int*) (deduction ok, T=U1)
- f2(U2,int*) ==> f3(T, X<T>::type*) (deduction ok, T=U2 makes X<U2>::type* -> int*)
If this were true, neither one would be more specialized than the other.)

After studying Clang's partial ordering algorithm it is clear that they treat '3' above as if it was:

template<class T, class S> void f(T, S*); // 4

so deduction of some unique 'U' against 'typename X::type' will succeed -

  • f3(U1,X<U1>::type*) is treated as f3(U1, U2*) ==> f2(T,int*) (deduction not ok)
  • f2(U2,int*) ==> f3(T,S* [[X<T>::type*]]) (deduction ok, T=U2, S=int)

And so '2' is clearly more specialized than '3'.

↙厌世 2024-08-03 18:49:57

T1 的转换参数列表(Q
插入):(Q,类型名称
常量::类型*)。 的类型
参数为 AT = (Q, void*)

我想知道这是否真的是一个正确的简化。 当您综合类型Q时,您是否可以为Const想出一个专门化来确定模板专门化的顺序?

template <>
struct Const<Q> { typedef int type; }

这意味着 T2 至少不如 T1 专业化,因为 void* 参数与 T1 不匹配任何给定模板参数的第二个参数。

Transformed parameter-list for T1 (Q
inserted): (Q, typename
Const::type*). The types of the
arguments are AT = (Q, void*)

I wonder if that really is a correct simplification. When you synthesise the type Q, are you allowed to conjure up a specialization for Const for the purposes of determining the ordering of template specliazation?

template <>
struct Const<Q> { typedef int type; }

This would imply that T2 is not at least as specialized as T1 because a void* parameter does not match T1's second parameter for any given template parameters.

凉世弥音 2024-08-03 18:49:57

编辑:请忽略这篇文章 - 在研究了 Doug Gregor 实现的部分排序的 clang 算法之后(尽管在撰写本文时它只是部分实现 - 似乎与 OP 问题相关的逻辑已经足够充分地实现了) - 它看起来好像它将未推导的上下文视为另一个模板参数。 这表明带有显式 void* 参数的重载应该是更专门的版本,并且不应该存在歧义。 像往常一样,科莫是正确的。
现在至于标准中明确定义这种行为的措辞 - 那是另一回事......

因为这篇文章也发布在 comp.lang.c++.moderated 上,并且似乎也在那里造成了一些混乱 - 我想我会也在这里发布我对该小组的回答 - 因为讨论显然与此处提出的问题相关。

7 月 25 日下午 1:11,Bart van Ingen Schenau [电子邮件受保护]> 写道:

你在这里迈得太快了。 你怎么知道(编译器会知道)Const没有专门化? 这样 Const::type != void?

据我所知,编译器会将 A 的参数列表转换为:AT=(Q,*)。 要使用这些参数调用 B 需要隐式转换(* 到 void*),因此 A 不如 B 专业化。

我认为这是不正确的。 当检查哪个功能更多时
专门化(在部分排序期间),编译器将转换
参数列表为 (Q, void*) - 即它实际上实例化了相关的
模板(最佳匹配)并在其中查找“type”的值 - 在本例中,基于
在主模板上,它将为 void*。

关于您关于部分专业化的观点 - 在检查时
哪个模板比另一个模板更专业,是唯一可以使用的类型
是唯一生成的类型 - 如果此时还有其他专业化
声明的实例化(当重载解析完成时)
他们将被考虑。 如果您稍后添加它们,它们应该被选中
你将违反 ODR(根据 14.7.4.1)

部分/显式专业化也将在
候选集的形成 - 但这次使用实际参数的类型
到函数。 如果(X 的)最佳匹配的部分特化结果是
对于某些函数类型具有更好的隐式转换序列
参数,那么我们永远不会进入部分排序阶段,并且
“更好”的功能将被选择(在使其成为部分功能之前)
排序阶段)

这是一个示例,其中包含有关各个步骤中应该发生的情况的注释:

    template<class T, bool=true> struct X;  // Primary

    template<class T> struct X<T,true> { typedef T type; };  // A
    template<> struct X<int*,true> { typedef void* type; };  // B


    template<class T> void f(T,typename X<T>::type); //1
    template<class T> void f(T*,void*); //2


    int main()
    {
      void* pv;
      int* pi;


      f(pi,pi);   
      // two candidate functions: f1<int*>(int*,void*),  f2<int>(int*,void*)
      // Note: specialization 'B' used to arrive at void* in f1
      // neither has a better ICS than the other, so lets partially order
      // transformed f1 is f1<U1>(U1,X<U1,true>::type) --> f1<U1>(U1,U1) 
      //       (template 'A' used to get the second U1)
      // obviously deduction will fail (U1,U1) -> (T*,void*)
      // and also fails the other way (U2*, void*) -> (T,X<T>::type)
      // can not partially order them - so ambiguity 




      f(pv,pv);  
      // two candidate functions: f1<void*>(void*,void*), f2<void>(void*,void*)
      // Note: specialization 'A' used to arrive at second void* in f1
      // neither has a better ICS than the other, so lets partially order
      // transformed f1 is f1<U1>(U1,X<U1>::type) --> f1<U1>(U1,U1) 
      //       (template 'A' used to get the second U1)
      // obviously deduction will fail (U1,U1) -> (T*,void*)
      // and also fails the other way (U2*, void*) -> (T,X<T>::type)
      // can not partially order them - so ambiguity again             

    }

还值得一提的是,如果主模板没有定义 - 那么 SFINAE 在部分排序阶段运行,
两者都不能从对方推断出来,因此会产生歧义。

此外,如果您添加另一个模板,如果这些函数中的任何一个的实例化点移动到翻译单元中的其他位置,那么这将导致另一个匹配,那么您显然会违反 ODR。

7 月 25 日下午 1:11,Bart van Ingen Schenau [电子邮件受保护]> 写道:

首先,更加专业化意味着这些类型更少
该模板可以通过重载决策来选择。
使用这个,部分排序的规则可以总结为:
找到 A 的类型,使得 A 可以被调用,但 B 不能被调用,或者重载
解析更倾向于调用 A。如果可以找到该类型,则 B 更合适
比 A 更专业。

这里没有争论。
但根据目前的规则,OP 的示例必须是
模糊的。


最后,这里是对 litb 提出的两个具体问题的明确、明确的答案:

1) 现在将使用为第一个参数推导的 T 值吗?
是的 - 当然,它必须这样做,它正在做模板参数推导 -
必须维护“链接”。

2)现在,为什么实现说第二个更专业?
因为他们错了;)

我希望这可以解决问题 - 如果有任何不清楚的地方,请告诉我:)

编辑:
litb 在他的评论中提出了一个很好的观点 - 也许指出主模板总是会得到
用于具有唯一生成类型的实例化的声明太强了。
在某些情况下,不会调用主模板。
我的意思是,当发生部分排序时,一些独特的生成类型是
用于匹配最佳专业化。 你是对的,它不一定是主模板。
我已经编辑了上述语言来做到这一点。
他还提出了关于在实例化点之后定义更好的匹配模板的问题。
根据实例化点部分,这将违反 ODR。


该标准规定,一旦创建了 A/P 对(使用 temp.func.order 中描述的转换规则),它们就会使用模板参数推导 (temp.deduct) 相互推导 - 并且该部分处理在非推导上下文的情况下,实例化模板及其嵌套类型,触发实例化点。 temp.point 部分处理 ODR 违规(无论翻译单元内的实例点如何,部分排序的含义都不应改变)。 我仍然不确定这种混乱来自哪里? – Faisal Vali 1 小时前 [删除此评论]

litb:“请注意,SFINAE 规则并未明确涵盖将 Q 放入 Const::type 来构建参数的步骤。
SFINAE 规则适用于参数推导,将 Q 放入函数模板函数参数列表的段落位于 14.5.5.2。'

这里必须使用 SFINAE 规则 - 怎么可能不使用呢?
我觉得它已经足够暗示了——我不会否认它可以更清楚,虽然我鼓励委员会澄清
这 - 我认为不需要澄清就能充分解释你的例子。

让我提供一种链接它们的方法。
来自(14.8.2):
“当指定显式模板参数列表时,模板参数必须与
模板参数列表,并且必须产生有效的函数类型,如下所述; 否则类型推导
失败”

来自 (14.5.5.2/3)
“使用的变换是:
— 对于每个类型模板参数,合成一个唯一的类型并将其替换为每次出现的
在我看来,上面的引用意味着,

一旦为每个模板参数“创建”唯一的生成类型,函数声明就必须是
通过显式将唯一类型作为模板参数提供给我们的函数模板来实例化。 如果这导致无效
函数类型,那么不仅仅是转换,更重要的是后续必要的模板参数推导
部分排序功能失败。

来自(14.5.5.2/4)
“使用转换后的函数参数列表,针对其他函数模板执行参数推导。转换后的模板
至少与其他当且仅当一样专门化,推导成功并且推导的参数类型
是精确匹配(因此推导不依赖于隐式转换)。”

如果转换后的函数参数列表导致替换失败,那么我们就知道推导不可能成功。
由于演绎没有成功,它不像另一个那么专业——这就是我们继续进行所需知道的一切
对两者进行部分排序。

litb:我也不确定在这种情况下会发生什么:template; 结构体A;
模板<类型名称T> void f(T, 类型名 A::type); 模板<类型名称 T> void f(T*, typename A::type); 当然,
这本来是有效的代码,但是执行 A::type 时,它​​会失败,因为在
模板定义上下文,A 尚未定义”
另请注意,没有为由此产生的模板实例化定义 POI
尝试确定排序时的替换类型(部分排序不依赖于
在任何情况下。 它是所涉及的两个函数模板的静态属性)。
我认为这看起来像是标准中的一个问题,需要修复。

好吧——我想我明白了我们看待事物的不同之处。 如果我理解正确的话,你是这么说的
当这些函数模板被声明时,编译器会跟踪它们之间的部分顺序,
无论重载解析是否被触发以在它们之间进行选择。
如果这就是您的解释方式,那么我就明白为什么您会期望您描述的上述行为。
但我认为该标准从未要求或强制这样做。

现在,标准很清楚,部分排序与调用函数时使用的类型无关(我相信
当您将其描述为静态属性并且它与上下文无关时,这就是您所指的)。

标准也明确表示它只关心函数模板之间的偏序(调用偏序)
在重载决策过程中(13.3.3/1)当且仅当它无法根据 ICS 选择更好的函数或
如果一个是模板而另一个不是。 [类模板部分特化的部分排序是一个单独的问题
在我看来,使用需要实例化该特定类的相关上下文(其他模板定义)。]

因此,在我看来,由于在重载时会调用函数模板的部分排序机制
执行解析时,它必须使用可用上下文的相关部分(模板定义和专业化)
在完成重载决策时。

因此,根据我的解释,根据上面使用“模板结构 A”的示例,代码是有效的。
部分排序不是在定义上下文中完成的。 但是如果/当你碰巧调用重载解析时
通过编写对 f((int*)0,0) 的调用来在两个函数之间进行调用 - 并且此时编译器要么
尝试组装候选声明或对它们进行部分排序(如果到达部分排序步骤)
如果函数类型中出现无效的表达式或类型,SFINAE 会帮助我们并告诉我们
我们认为模板推导失败(就部分排序而言,这意味着一个
如果我们甚至无法转换模板,就不可能比另一个更专业)。

现在关于 POI - 如果您像我一样确信转换后的函数类型应该
使用显式提供的模板参数列表(使用唯一生成的类型)表示隐式实例化
那么以下标准引用是相关的:

14.6.4.1/1 对于函数模板特化、成员函数模板特化或
类模板的成员函数或静态数据成员(如果特化是隐式实例化的)
因为它是从另一个模板专业化以及引用它的上下文中引用的
取决于模板参数,特化的实例化点就是实例化点
所包含的专业化。

我的解释是,转换后的函数类型和原始函数类型的 POI 是
与实际函数调用创建的那些函数的 POI 相同。

litb:因为部分排序只是
参数语法形式的属性(即“T*”与“T(*)[N]”),
我会投票赞成修改规范(例如“如果 Q 出现在
命名类型的限定 ID,则命名的类型为“Q”)
或者说命名的类型是另一种独特的类型。
这意味着在 template中, void f(T, 类型名 Const::type*);
例如,参数列表是 (Q, R*)。
与模板相同 void f(T*, 类型名 ConstI::type);
arg 列表将为 (Q*, R)。 当然,非类型参数也需要类似的规则。
不过,我必须考虑一下并制作一些测试用例,看看这是否会产生自然的顺序。

啊 - 现在你提出了一个可能的解决方案来解决有利于我们的歧义
所有人都直观地期望 - 这是一个单独的问题,虽然我喜欢你前进的方向,
和你一样,在宣布它的可行性之前,我也必须先考虑一下它。

感谢您继续讨论。 我希望 SO 不仅仅限制您发表评论。

由于您可以编辑我的帖子,因此如果更容易的话,请随时在帖子中回复。

Edit: Please disregard this post - After studying clangs algorithm for partial ordering as implemented by Doug Gregor (even though it is only partially implemented as of this writing - it seems that the logic that's relevant to the OP's question is implemented adequately enough) - it appears as if it treats the undeduced context as just another template parameter. Which suggests that the overload with the explicit void* argument should be the more specialized version and there should be no ambiguity. As usual Comeau is correct.
Now as for the wording in the standard that clearly defines this behavior - that's another matter ...

Since this post was also posted on comp.lang.c++.moderated, and seems to be causing some confusion there too - i thought i'd post my answer to that group here too - since the discussion is obviously relevant to the question asked here.

On Jul 25, 1:11 pm, Bart van Ingen Schenau <[email protected]> wrote:

You are going one step too fast here. How do you know (and would the compiler know) that there is no specialisation of Const<Q> such that Const<Q>::type != void?

As far as I can see, the compiler would transform the parameter-list of A to: AT=(Q, <unknown>*). To call B with these parameters requires an implicit conversion (<unknown>* to void*) and therefore A is less specialised than B.

I believe this is incorrect. When checking to see which function is more
specialized (during partial-ordering), the compiler transforms the
parameter-list to (Q, void*) - i.e. it actually instantiates the relevant
template (best matching) and looks inside it for the value of 'type' - in this case, based
on the primary template, it will be void*.

Regarding your point concerning partial specialization - when checking for
which template is more specialized than the other, the only type that can be used
is the unique generated type - if there are other specializations at the point
of instantiation of the declaration (when overload resolution is being done)
they will be considered. If you add them later, and they should get selected
you will be violating the ODR (according to 14.7.4.1)

The partial/explicit specializations will also get considertaion during
formation of the candidate set - but this time using the types of the actual arguments
to the function. If the best matching partial specialization (of X) results in a
function type that has a better implicit conversion sequence for some
parameter, then we never make it to the partial-ordering phase, and that
"better" function will get selected (before making it to the partial
ordering phase)

Here is an example with comments about what should be going on at various steps:

    template<class T, bool=true> struct X;  // Primary

    template<class T> struct X<T,true> { typedef T type; };  // A
    template<> struct X<int*,true> { typedef void* type; };  // B


    template<class T> void f(T,typename X<T>::type); //1
    template<class T> void f(T*,void*); //2


    int main()
    {
      void* pv;
      int* pi;


      f(pi,pi);   
      // two candidate functions: f1<int*>(int*,void*),  f2<int>(int*,void*)
      // Note: specialization 'B' used to arrive at void* in f1
      // neither has a better ICS than the other, so lets partially order
      // transformed f1 is f1<U1>(U1,X<U1,true>::type) --> f1<U1>(U1,U1) 
      //       (template 'A' used to get the second U1)
      // obviously deduction will fail (U1,U1) -> (T*,void*)
      // and also fails the other way (U2*, void*) -> (T,X<T>::type)
      // can not partially order them - so ambiguity 




      f(pv,pv);  
      // two candidate functions: f1<void*>(void*,void*), f2<void>(void*,void*)
      // Note: specialization 'A' used to arrive at second void* in f1
      // neither has a better ICS than the other, so lets partially order
      // transformed f1 is f1<U1>(U1,X<U1>::type) --> f1<U1>(U1,U1) 
      //       (template 'A' used to get the second U1)
      // obviously deduction will fail (U1,U1) -> (T*,void*)
      // and also fails the other way (U2*, void*) -> (T,X<T>::type)
      // can not partially order them - so ambiguity again             

    }

It's also worth mentioning that if the primary template does not have a definition - then SFINAE operates during the partial ordering phase,
neither can be deduced from the other, and ambiguity should result.

Also if you add another template that would lead to another match if the point of instantation of either of those functions is moved elsewhere in the translation unit you will clearly violate the ODR.

On Jul 25, 1:11 pm, Bart van Ingen Schenau <[email protected]> wrote:

First, being more specialised means that these are fewer types where
that template can be selected by overload resolution.
Using this, the rules for partial ordering can be summarised as: Try to
find a type for A such that A can be called but B not, or overload
resolution prefers to call A. If that type can be found, then B is more
specialised than A.

No argument here.
But based on the rules as they are currently, the OP's example has to be
ambiguous.


Finally, here are explicit, unambiguous answers to the two specific questions raised by litb:

1) Will this now use the value of T deduced for the first parameter?
Yes - of course, it has to, it is doing template argument deduction -
the 'links' have to be maintained.

2) Now, why do the implementations say that the second is more specialized instead?
Because they are wrong ;)

I hope this puts the issue to rest - Please let me know if there is anything that is still unclear :)

Edit:
litb raised a good point in his comment - perhaps stating that the primary template will always get
used for the instantiation with the unique generated type is too strong a statement.
There are instances where the primary template will not be called.
What I am getting at is that when partial ordering is occuring, some unique generated type is
used to match the best specialization. You're right, it doesn't have to be the primary template.
I have edited the above language to do so.
He also raised an issue regarding defining a better matching template after the point of instantation.
That will be a violation of the ODR according to the section on point of instantiation.


The standard says that once the A/P pairs are created (using the rules of transformation as described in temp.func.order) they are deduced against each other using template argument deduction (temp.deduct)- and that section handles the case of non-deduced contexts, instantiating the template and its nested type, triggering points of instantiations. The temp.point section handles the ODR violations (the meaning of partial ordering should not change regardless of the points of instantation within a translation unit). I'm still not sure where the confusion is coming from? – Faisal Vali 1 hour ago [delete this comment]

litb: "Note that the step that puts Q into Const::type to build the arguments is not covered explicitly by the SFINAE rule.
The SFINAE rules work with argument deduction, put the paragraphs that put Q into the function template function parameter list are at 14.5.5.2.'

The SFINAE rules have to be used here - how could they not be?
I feel it is sufficiently implied - i won't deny that it could be clearer, and while i encourage the committee to clarify
this - i don't think it needs to be clarified to interpret your example sufficiently.

Let me provide one way to link them.
From (14.8.2):
"When an explicit template argument list is specified, the template arguments must be compatible with the
template parameter list and must result in a valid function type as described below; otherwise type deduction
fails"

From (14.5.5.2/3)
"The transformation used is:
— For each type template parameter, synthesize a unique type and substitute that for each occurrence of
that parameter in the function parameter list, or for a template conversion function, in the return type."

In my mind, the above quote implies that once you "create" unique generated types for each template parameter, the function declaration has to be
implicity instantiated by explicitly supplying the unique types as template arguments to our function template. If this results in an invalid
function type, then not only the transformation, but more importantly the subsequent template argument deduction necessary to
partially order the function fails.

From (14.5.5.2/4)
"Using the transformed function parameter list, perform argument deduction against the other function template. The transformed template
is at least as specialized as the other if, and only if, the deduction succeeds and the deduced parameter types
are an exact match (so the deduction does not rely on implicit conversions)."

If the transformed function parameter list leads to substitution failure, then we know deduction could not have succeeded.
And since deduction did not succeed, it is not as specialized as the other - that is all we need to know to proceed
in partial ordering the two.

litb: I'm also not sure what happens in this case: template<typename T> struct A;
template<typename T> void f(T, typename A<T>::type); template<typename T> void f(T*, typename A<T>::type); surely,
that's indended to be valid code, but doing A::type, it will fail because at the
template definition context, A isn't defined yet"
Also note that there is no POI defined for template instantiations resulting from this
kind of substitution while trying to determine an ordering (partial ordering does not depend
on any context. It's a static property of two function templates involved).
I think this looks like a problem in the Standard which needs to be fixed.

Ok - i think i see where we are seeing things differently. If i understand you correctly, you are saying that
as these function templates get declared, the compiler is keeping a track of the partial ordering amongst them,
regardless of overload resolution ever getting triggered to select between them.
If that is how you interpret it, then i can see why you would expect the above behavior you describe.
But I do not think that the standard ever requires or mandates that.

Now, the standard is clear that the partial ordering is agnostic to the type that is used in calling the function (I believe
this is what you are referring to when you describe it as a static property and it being context independent).

The standard is also clear that it only cares about partial ordering (invokes partial ordering) between function templates
during the process of overload resolution (13.3.3/1) if and only if it could not pick the better function based on ICS's or
if one is a template and the other is not. [Partial ordering of class template partial specializations is a separate issue
and in my mind uses the relevant context (other template definitions) that requires the instantiation of that particular class.]

Thus, in my opinion, since the machinery of partial ordering of function templates is invoked when overload
resolution is performed, it has to use a relevant portion of the context (template definitions and specializations) available
at the point when the overload resolution is being done.

So based on my interepretation, according to your example using 'template struct A' above, the code is valid.
The partial ordering is not done at the definition context. But if/when you happen to invoke overload resolution
between the two functions by writing a call to f((int*)0,0) - and at that time when the compiler either
tries to assemble a candidate declaration or partially order them (if it gets to the partial-ordering step)
if an invalid expression or type results as part of the function type, SFINAE helps us out and tells
us that template deduction fails (as far as partial ordering is concerned, that implies that one
cannot be more specialized than the other if we could not even transform the template).

Now as regards POIs - if you are convinced, as I am, that the transformed function types are supposed to
represent implicit instantiations using explicitly supplied template argument lists (using the uniquely generated types)
then the following standard quotes are relevant:

14.6.4.1/1 For a function template specialization, a member function template specialization, or a specialization for a
member function or static data member of a class template, if the specialization is implicitly instantiated
because it is referenced from within another template specialization and the context from which it is referenced
depends on a template parameter, the point of instantiation of the specialization is the point of instantiation
of the enclosing specialization.

The way I interpret this is that the POI of the transformed function type and the origianl function type is the
same as the POI for those functions created by the actual function call.

litb: Since partial ordering is rather only
a property of the syntactic form of parameters (i.e "T*" against "T(*)[N]"),
i would vote for amending the specification (like "if Q appears in a nested name specifier of
a qualified-id naming a type, then the type named is "Q")
Or saying that the type named is another unique type.
This means that in template<typename T> void f(T, typename Const<T>::type*);
the argument list is (Q, R*), for example.
Same for template<typename T> void f(T*, typename ConstI<sizeof(T)>::type);
the arg lisst would be (Q*, R). A similar rule would be needed for non-type parameters, of course.
I would have to think about it and make some test cases to see if this would yield natural orderings, though.

Aah - now you are suggesting a possible solution that resolves the ambiguity in favor of what we
all intuitively expect - this is a separate problem, and while I like the direction you are heading in,
like you, I too would have to put some thought into it before proclaiming its workability.

Thanks for continuing the discussion. I wish SO didn't just limit you to placing comments.

Since you can edit my posts, please feel free to respond within the post if that is easier.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文