偏序具有函数模板undeduced背景(Partial ordering with function

2019-06-26 18:23发布

在阅读了另一个问题,我来带偏序的一个问题,这是我砍倒在下面的测试案例

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*) 但偏序(根据科莫和GCC)现在说第二个模板是更加专业化。 但为什么?

让我经历偏序,并显示在那里我有疑问。 可Q为用于根据确定部分排序的独特由上型14.5.5.2

  • 变换参数列表为T1 (Q插入): (Q, typename Const<Q>::type*) 类型的参数是AT = (Q, void*)
  • 变换参数列表为T2 (Q插入): BT = (Q, void*)其也是类型的参数。
  • 未转化的参数列表为T1(T, typename Const<T>::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 。 类型推演平凡成功为AT针对T2

  • BT针对T1{ (Q, T) , (void*, typename Const<T>::type*) } 。 它会发现, TQ ,太这里。 typename Const<T>::type*是一个未推导出上下文,并且因此它不会被用来推断任何东西。


这是我的第一个问题:这会不会现在使用的值T推导出第一个参数? 如果答案是否定的,那么第一个模板是更加专业化。 这不可能是这样,因为GCC和科莫都表示,第二模板是更加专业化的,我不相信他们是错误的。 因此,我们认为“是”,并插入void*T 。 该段( 14.8.2.4 )说:“扣除每对独立完成,然后将结果合并”,也是“在某些情况下,但是,价值不参与型扣,而是使用的模板参数的值是要么推断其他地方或明确指定“。 这听起来像“是”了。

因此扣除成功太,对于每个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*)'

Answer 1:

这里是我走在这。 我同意查尔斯·贝利是不正确的步骤是从去Const<Q>::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

我没有看到需要的第二合成参数的任何措辞T1void* 。 我不知道任何先例,在其他情况下的两种。 类型Const<Q>::Type*是C ++型系统内完全有效的类型。

所以,现在我们进行扣除步骤:

Q2到T1

我们试图推导出T1模板参数,所以我们有:

  • 参数1: T被推断为是Q
  • 参数2:Nondeduced背景

即使参数2是非推断背景下,演绎仍然成功是因为我们有一个T的值

Q1至T2

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

  • 参数1: T被推断为是Q
  • 参数2: void*不匹配Const<Q>::Type*所以扣失败。

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

T1的合成参数可以用来专门T2,但是反之则不行。 因此T2比T1更专门等是最佳的功能。


更新1:

只是以覆盖有关Poing的Const<Q>::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<int>::type用于当我们正在执行通常的重载决策规则,但不是当我们得到的局部过载规则。 它不会是正确的选择任意专业化Const<Q>::type 。 它可能不是很直观,但是编译器是很高兴有一个synthasized型形式的Const<Q>::type*和类型推导过程中使用它。


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

Const模板与一些值实例化I ,它递归地实例化本身直到I达到0的部分特时,这是Const<T,0>被选择。 如果我们有一个编译器综合了一些真正的类型,函数的参数,那么将编译器选择数组索引什么样的价值? 说10? 那么,这将是精为上面的例子,但它不会匹配部分特Const<T, 10 + 1>其中,在概念上至少,将导致在初级的递归实例的无限数量。 无论价值在于它选择我们可以修改成该值+ 1结束条件,然后我们不得不在局部排序算法的无限循环。

我看不出偏序算法能够正确实例Const找什么type真的是。



Answer 2:

编辑:经过研究锵的其部分排序算法的实现(由道格·格雷戈尔),我已经跟原来的例子不是“意”是暧昧的海报剩下的同意-尽管这个标准是不是一样清晰它可能是约在这种情况下会发生什么。 我已经编辑这篇文章来说明我的想法修订(为我自己的利益和参考)。 特别锵的算法澄清,“ typename Const<T>::type ”在部分排序步骤不被翻译成“无效” - ,并且每个A / P对推导出相互独立的。

起初我不知道为什么以下被认为是不明确的:

        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.)

但下面不会有歧义:

        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

(人们可以期望它是不明确的原因是,如果以下情况发生:
- 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*)
如果这是真的,没有人会比其他更专业的。)

研究锵的部分排序算法后很显然,他们对待“3”以上,如果它是:

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

所以反对“类型名称X ::型”一些独特的“U”的扣除一定会成功 -

  • 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)

于是“2”显然比“3”更加专业化。



Answer 3:

变换参数列表为T1(Q插入):(Q,类型名称CONST ::类型*)。 类型的参数是AT =(Q,无效*)

我不知道这是否真的是一个正确的简化。 当你合成的类Q ,你允许想起了一个专业化Const确定模板specliazation的排序的目的是什么?

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

这将意味着T2没有专门的至少为T1 ,因为一个void*参数不匹配T1对于任何给定的模板参数的第二个参数。



Answer 4:

编辑:请忽略此职位 - 学习红clangs算法部分排序由道格·格雷戈尔实施后(即使它是在写这篇文章的只是部分实现的 - 这似乎是实现自己相关的业务方案的问题的逻辑充分就够了) - 它看起来好像对待undeduced上下文只是一个模板参数。 这表明有明确void *的参数超载应该是更为特殊的版本,并应该有任何含糊。 像往常一样,科莫是正确的。 现在,作为在明确规定这种行为的标准措辞 - 那是另一回事...

由于这个帖子也被张贴在comp.lang.c ++主持,并似乎有造成一些混乱过 - 我想我会后我的回答这个群体也在这里 - 因为讨论的是在这里问的问题明显相关。

On Jul 25, 1:11 pm, Bart van Ingen Schenau <b...@ingen.ddns.info> 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.

我认为这是不正确。 当检查,看看哪些功能是更专业的(部分排序时),编译器变换参数列表(Q, void*) -也就是说,它实际上会实例相关的模板(最佳匹配),看起来里面它的价值“类型” - 在这种情况下,基于主模板,这将是无效*。

关于你提到的有关部分专门点 - 检查这些模板是比其他更专业的时候,可以使用的唯一类型是唯一生成的类型 - 如果有在声明的实例化点是其他各种专业(过载时的分辨率正在做的),他们将予以考虑。 如果添加他们后,他们应该得到选择,你将违反ODR(根据14.7.4.1)

局部/明确的专业化也将形成候选集的过程中得到considertaion - 但使用类型的实际参数的函数这个时候。 如果有一些参数更好的隐式转换序列,那么我们永远不会使它的部分排序阶段,而一个功能型的结果(的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在偏序阶段工作,既可以从其他被推断,模糊性应该导致。

此外,如果你加入另一个模板,如果任这些功能instantation的点在翻译单元,你会清楚地违反了ODR移到其它地方,这将导致另一场比赛。

On Jul 25, 1:11 pm, Bart van Ingen Schenau <b...@ingen.ddns.info> wrote:

首先,是更专业化意味着这些地方的这个模板可以通过重载决议选择种类较少 。 用这种方法,对于部分排序的规则可以概括为:试图找到一个类型为A使得A可以调用,但乙没有,或过载分辨率更喜欢称A.如果可以找到这种类型的,那么B更专业A.比

这里没有说法。 但根据规则,因为它们是目前,OP的例子已是不明确的。


最后,这里是明确的,毫不含糊的答案被litb提出两个具体的问题:

1)请问这个现在使用的T推导出的第一参数的值?
是的 - 当然,它必须,它是做模板参数推导 - 的“链接”必须保持。

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

我希望把这个问题休息了 - 请让我知道,如果有任何事情目前还不清楚:)

编辑:litb在他的评论中提出了一个很好的点 - 也许,说明了主模板总是习惯与独特生成的类型实例化太强的声明。
有一些情况,其中主模板将不会被调用。
我所得到的是,当部分排序正在发生,一些独特生成的类型来匹配最佳的专业化。 你说得对,它并没有成为主要的模板。 我已经编辑了以上的语言这样做。 他还提出了关于instantation后点确定一个更好的匹配模板的问题。 这将是根据对实例的点的部分违反了ODR的。


标准说,一旦A / P对被创建(使用变换规则如在temp.func.order描述)他们正在使用模板参数推导(temp.deduct)彼此抵靠推断 - 这部分处理的情况下非上下文推导出,实例化模板及其嵌套类型,引发实例化点。 该temp.point部分处理违反ODR(部分排序的含义不应该翻译单元内无论instantation的点的变化)。 我仍然不知道在哪里的困惑是哪里来的? - 费萨尔瓦利1小时前[删除此评论]

litb:“请注意,使Q为常量::类型建立参数的步骤不是由SFINAE规则明确覆盖的SFINAE规则与参数推导工作,把那把Q入函数模板函数参数列表是段落。在14.5.5.2“。

该SFINAE规则必须要在此处使用 - 他们怎么能不这样吗? 我觉得这充分暗示 - 我不否认,它可能是更清晰,而我鼓励委员会澄清这一点 - 我不认为它需要加以澄清,以充分理解你的榜样。

让我提供链接他们的一种方式。 从(14.8.2):“当指定了显式模板参数列表,则模板参数必须与模板参数列表兼容,并且必须产生有效的功能类型,如下所述;否则键入扣失败”

从(14.5.5.2/3)“中所使用的变换是: - 对于每一种类型的模板的参数,合成了一种独特类型和替代,对于在函数参数列表参数的每次出现,或者用于模板转换功能,在返回类型。”

在我看来,以上报价意味着,一旦你“创造”的独特生成的类型为每个模板参数,函数声明必须由含蓄明确提供独特的类型作为模板参数我们的函数模板实例化。 如果这会导致一个无效的函数类型,则不仅是转型,但更重要的是后续的模板参数推导需要部分订购功能失效。

从(14.5.5.2/4)“使用转换函数参数列表,对其他功能进行模板参数推导。转化的模板是至少为专门为其他,当且仅当 ,扣成功,并且推断参数类型严格匹配(所以扣不依赖于隐式转换)。”

如果转换函数参数列表导致代换失败,那么我们就知道扣是不会成功的。 而且,由于扣没有成功,它并不像专门为其他 - 这是所有我们需要知道在偏序两个继续。

litb:我也不能肯定在这种情况下会发生什么: 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); 当然,这是indended是有效的代码,但这样做A ::类型,因为模板定义背景下,没有定义它会失败呢。”另外请注意,没有从这种替代导致模板实例定义兴趣点尝试确定排序(部分排序不依赖于任何情况下,它是涉及到两个函数模板的静态属性),我认为这看起来像在标准,该标准需要固定的一个问题。

好了 - 我想我看到我们不同看到的东西。 如果我理解正确的话,你是说,因为这些函数模板得到声明,编译器是保持他们之间的偏序的轨道,无论重载解析有朝一日能引发他们之间进行选择。 如果这是你如何解释它,那么我可以看到你为什么会想到你所描述的上述行为。 但我不认为标准不断要求或强制要求。

现在,这个标准是明确的是,部分排序是不可知的是在调用该函数使用的类型(我相信这是你是指,当你把它描述为一个静态属性是什么,它是独立的情况下)。

该标准也明确表示,它只是重载解析(13.3.3 / 1)的过程中关心函数模板之间关于偏序(调用部分排序),当且仅当它不能挑基于ICS的,或者一个更好的功能是一个模板,另一种是没有。 [类模板偏特化的部分排序是一个独立的问题,在我的脑海使用需要特定的类的实例化的有关情况(其他模板定义)。]

因此,在我看来,因为函数模板部分排序的机器当执行重载解析被调用时,它在该点使用上下文提供相关的部分(模板定义和专业)时正在做的重载解析。

因此,基于我的interepretation,根据使用的“样板结构A”你上面的例子中,代码是有效的。 偏序不是在定义范围内完成。 但是,如果/当你碰巧写到f调用((INT *)0,0)来调用两个函数重载决议 - 在编译的时候要么试图组装一个候选人声明或者部分责令那个时候(如果它得到了部分排序步骤)如果一个无效的表达式或类型的结果作为函数类型的一部分,SFINAE帮助我们并告诉我们,模板推演失败(只要局部排序而言,这意味着一个不能更专业比其他的,如果我们甚至不能转换模板)。

现在,至于兴趣点 - 如果你相信,因为我,是变换函数类型应该代表使用显式提供模板参数列表(使用唯一生成的类型),那么下面的标准报价是相关的隐式实例:

14.6.4.1/1对于函数模板专业化,成员函数模板特,或为一个成员函数或类模板的静态数据成员的特殊化,如果专业化被隐式地实例化,因为它是从另一个模板专业化和内引用从该被引用上下文依赖于模板参数,专业化的实例化点是封闭专业化实例化点。

我解释这个问题的方法是变换函数类型和原著功能类型的POI是一样的POI由实际的函数调用创建的那些功能。

litb:由于部分排序是相当只有a property of the syntactic form of parameters (ie "T*" against "T(*)[N]"),我将投票修改说明书(如“如果Q出现在嵌套一个合格的-ID命名的类型名称符,然后命名类型是“Q”),或者说,一个名为类型是另一种独特的类型。 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.我会考虑一下,做一些测试案例,看看这是否会产生自然排序,虽然。

啊哈 - 现在你的建议是解决有利于我们都直观地期望的模糊性可能的解决方案 - 这是一个单独的问题,虽然我很喜欢你的标题在,就像你的方向,我也将不得不把一些思考进入它宣布它的可操作性之前。

感谢您持续的讨论。 我愿意的话不只是限制你放置的意见。

既然你可以编辑自己的帖子,请随时免费的,如果是比较容易的后内作出答复。



文章来源: Partial ordering with function template having undeduced context