c++ - 具有非推导上下文的部分特化排序

标签 c++ templates language-lawyer partial-specialization partial-ordering

根据 [temp.class.order] §14.5.5.2,选择 t 的部分特化在这个例子中:

template< typename >
struct s { typedef void v, w; };

template< typename, typename = void >
struct t {};

template< typename c >
struct t< c, typename c::v > {};

template< typename c >
struct t< s< c >, typename s< c >::w > {};

t< s< int > > q;

相当于选择了 f 的重载在这个例子中:
template< typename >
struct s { typedef void v, w; };

template< typename, typename = void >
struct t {};

template< typename c >
constexpr int f( t< c, typename c::v > ) { return 1; }

template< typename c >
constexpr int f( t< s< c >, typename s< c >::w > ) { return 2; }

static_assert ( f( t< s< int > >() ) == 2, "" );

然而,GCC、Clang 和 ICC 都拒绝第一个例子,认为第一个例子不明确,但接受第二个例子。

更奇怪的是,第一个例子在 ::v 时有效替换为 ::w或相反亦然。非推导上下文 c::s< c >::显然正在考虑特化排序,这没有意义。

我是否遗漏了标准中的某些内容,或者所有这些实现都有相同的错误?

最佳答案

暂时切换到非常迂腐的模式,是的,我认为您在标准中遗漏了一些东西,不,在这种情况下应该没有任何区别。

所有标准引用均针对 N4527,即当前的工作草案。

[14.5.5.2p1] 说:

For two class template partial specializations, the first is more specialized than the second if, given the following rewrite to two function templates, the first function template is more specialized than the second according to the ordering rules for function templates (14.5.6.2):

  • the first function template has the same template parameters as the first partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the first partial specialization, and
  • the second function template has the same template parameters as the second partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the second partial specialization.


前往 [14.5.6.2p1]:

[...] Partial ordering of overloaded function template declarations is used in the following contexts to select the function template to which a function template specialization refers:

  • during overload resolution for a call to a function template specialization (13.3.3);
  • when the address of a function template specialization is taken;
  • when a placement operator delete that is a function template specialization is selected to match a placement operator new (3.7.4.2, 5.3.4);
  • when a friend function declaration (14.5.4), an explicit instantiation (14.7.2) or an explicit specialization (14.7.3) refers to a function template specialization.


没有提到类模板特化的部分排序。然而,[14.8.2.4p3] 说:

The types used to determine the ordering depend on the context in which the partial ordering is done:

  • In the context of a function call, the types used are those function parameter types for which the function call has arguments.
  • In the context of a call to a conversion function, the return types of the conversion function templates are used.
  • In other contexts (14.5.6.2) the function template’s function type is used.


尽管它回溯到 [14.5.6.2],但它确实说的是“其他上下文”。我只能得出结论,当将偏序算法应用于根据 [14.5.5.2] 中的规则生成的函数模板时,使用的是函数模板的函数类型,而不是参数类型列表,因为它会发生在函数中称呼。

所以,选择t的偏专业在您的第一个代码段中,不等同于涉及函数调用的情况,而是等同于采用函数模板地址的情况(例如),该地址也属于“其他上下文”:
#include <iostream>

template<typename> struct s { typedef void v, w; };
template<typename, typename = void> struct t { };

template<typename C> void f(t<C, typename C::v>) { std::cout << "t<C, C::v>\n"; }
template<typename C> void f(t<s<C>, typename s<C>::w>) { std::cout << "t<s<C>, s<C>::w>\n"; }

int main()
{
   using pft = void (*)(t<s<int>>);
   pft p = f;
   p(t<s<int>>());
}

(由于我们仍然处于极端迂腐的模式,我完全按照 [14.5.5.2p2] 中的示例重写了函数模板。)

不用说,这也编译打印t<s<C>, s<C>::w> .它产生不同行为的可能性很小,但我不得不尝试一下。考虑到算法的工作原理,如果函数参数是例如引用类型(在函数调用的情况下触发 [14.8.2.4] 中的特殊规则,但在其他情况下不会),则会有所不同,但是这样的形式不能出现在从类模板特化生成的函数模板中。

所以,这整个绕道并没有帮助我们,但是......它是language-lawyer问题,我们必须在这里有一些标准的报价...

有一些与您的示例相关的活跃核心问题:
  • 1157包含我认为相关的注释:

    Template argument deduction is an attempt to match a P and a deduced A; however, template argument deduction is not specified to fail if the P and the deduced A are incompatible. This may occur in the presence of non-deduced contexts. Notwithstanding the parenthetical statement in 14.8.2.4 [temp.deduct.partial] paragraph 9, template argument deduction may succeed in determining a template argument for every template parameter while producing a deduced A that is not compatible with the corresponding P.



    我不完全确定这是明确规定的;毕竟,[14.8.2.5p1] 说

    [...] find template argument values [...] that will make P, after substitution of the deduced values [...], compatible with A.



    和 [14.8.2.4] 全文引用 [14.8.2.5]。然而,很明显,当涉及非推导的上下文时,函数模板的偏序并不寻求兼容性,改变它会破坏很多有效的情况,所以我认为这只是标准中缺乏适当的规范.
  • 在较小程度上,1847与出现在模板特化参数中的非推导上下文有关。它引用了 1391决议;我认为该措辞存在一些问题 - 更多详情请参见 this answer .

  • 对我来说,所有这些都表明你的例子应该有效。

    和您一样,我对三个不同的编译器中存在相同的不一致这一事实很感兴趣。在我确认 MSVC 14 表现出与其他人完全相同的行为后,我更加感兴趣了。所以,当我有时间的时候,我想我会快速看看 Clang 做了什么;结果证明它一点也不快,但它产生了一些答案。

    与我们的案例相关的所有代码都在 lib/Sema/SemaTemplateDeduction.cpp 中.

    演绎算法的核心是 DeduceTemplateArgumentsByTypeMatch 功能;演绎的所有变体最终都会调用它,然后递归地使用它来遍历复合类型的结构,有时借助重载 DeduceTemplateArguments一组函数,还有一些 flags根据正在执行的特定类型的推导和正在查看的类型表单的部分来调整算法。

    关于这个函数的一个重要方面是它处理严格的演绎,而不是替代。它比较类型形式,为出现在推导上下文中的模板参数推导模板参数值,并跳过非推导上下文。它所做的唯一其他检查是验证模板参数的推导参数值是否一致。我在 the answer I mentioned above 中写了更多关于 Clang 在偏序期间进行演绎的方式的文章.

    对于函数模板的偏序,算法从 Sema::getMoreSpecializedTemplate 开始成员函数,它使用 enum TPOC 类型的标志确定正在进行偏序排序的上下文;枚举器是 TPOC_Call , TPOC_Conversion , 和 TPOC_Other ;不言自明。这个函数然后调用 isAtLeastAsSpecializedAs 两次,在两个模板之间来回,并比较结果。

    isAtLeastAsSpecializedAs 打开 TPOC 的值标志,在此基础上进行一些调整,最终直接或间接调用 DeduceTemplateArgumentsByTypeMatch .如果返回 Sema::TDK_Success , isAtLeastAsSpecializedAs 只做一项检查,以验证用于偏序的所有模板参数都具有值。如果这也很好,它会返回 true .

    这是函数模板的偏序。根据上一节中引用的段落,我期望类模板特化的偏序调用 Sema::getMoreSpecializedTemplate 具有适当构造的函数模板和标志 TPOC_Other ,一切从那里自然而然地流动。如果是这种情况,您的示例应该可以工作。惊喜:这不是发生的事情。

    类模板特化的部分排序始于 Sema::getMoreSpecializedPartialSpecialization .作为优化(危险信号!),它不合成函数模板,而是使用 DeduceTemplateArgumentsByTypeMatch 直接对类模板特化本身进行类型推导作为 P 的类型和 A .这可以;毕竟,这就是函数模板算法最终会做的事情。

    但是,如果在演绎过程中一切顺利,它就会调用 FinishTemplateArgumentDeduction (类模板特化的重载),它进行替换和其他检查,包括检查特化的替换参数 are equivalent to the original ones .如果代码正在检查部分特化是否匹配一组参数,这会很好,但在部分排序期间不好,并且据我所知,这会导致您的示例出现问题。

    所以,似乎Richard Corden's关于发生的事情的假设是正确的,但我不完全确定这是故意的。这对我来说更像是一种疏忽。我们如何最终让所有编译器以相同的方式运行仍然是个谜。

    在我看来,删除对 FinishTemplateArgumentDeduction 的两次调用来自 Sema::getMoreSpecializedPartialSpecialization 不会造成伤害,并且会恢复偏序算法的一致性。不需要额外的检查(由 isAtLeastAsSpecializedAs 完成)所有模板参数都有值,因为我们知道所有模板参数都可以从特化的参数中推导出来;如果不是,部分特化将无法匹配,因此我们不会首先进行部分排序。 (首先是否允许这种部分特化是 issue 549 的主题。Clang 对这种情况发出警告,MSVC 和 GCC 发出错误。无论如何,不​​是问题。)

    作为旁注,我认为所有这些都适用于 overload for variable template specializations以及。

    不幸的是,我没有为 Clang 设置构建环境,因此我目前无法测试此更改。

    关于c++ - 具有非推导上下文的部分特化排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31497357/

    相关文章:

    c++ - 不可预测的伪RNG

    c++ - 取消引用 50% 出界指针(数组的数组)

    c++ - setter 和 getter 的模板

    c++ - const 和非 const 键有什么区别?

    c++ - std::poisson_distribution 中可能存在的构造错误

    c++ - 非均匀采样的CUDA重采样

    c++ - 如何从另一个指针指向的内存地址获取数据?

    c++ - SFINAE 函数的 "catch-all"?

    C++: "Inferring"来自依赖类型的模板类型

    c - C语言中为什么char是1个字节