【问题标题】:Several issues about deducing conversion function template arguments关于推导转换函数模板参数的几个问题
【发布时间】:2021-01-05 06:24:05
【问题描述】:

转换函数模板推导模板参数的规则在temp.deduct.conv部分。

第一条规则指定对应的P和A是什么,第一个问题在这里:

模板参数推导是通过将转换函数模板的返回类型(称为 P)与 正在查找的转换函数 ID 的转换类型 ID 指定的类型(称为A) 如 [temp.deduct.type] 中所述。如果在重载解析期间构造转换函数 ID ([over.match.funcs]),则适用以下转换。

#include <iostream>
struct C{
    template<typename Type>
    operator Type(){
        Type a{};
        /*a = 1;*/
        return a;
    }
};

int main(){
    int const& rf = C{};
}

对于这个例子,P 应该是Type,但是 A 是什么?似乎只有一条规则与之相关:
over#match.funcs.general-7

在每个考虑类 S 的转换函数来初始化 T 类型的对象或引用的情况下,候选函数包括在 S 中搜索 conversion-function-id 运算符 T 的结果强>。

我不知道这里的 A 是只是 int const&amp; 还是 为此初始化定义的任何允许的类型

如果A由前者决定,则P为Type,A为int const&amp;

根据项目符号 2:

如果 P 是引用类型,则使用 P 引用的类型代替 P 进行类型推导以及在本小节的其余部分中对 P 的任何进一步引用或转换。

因此,转换后的 P 仍然是Type,那么这样的 P/A 对将经历第 3 条

如果 A 不是引用类型:

  • [...]

因为 A 是引用类型,所以跳过第 3 条,继续第 4 条

如果 A 是 cv 限定类型,则忽略 A 类型的顶级 cv 限定符进行类型推导。如果A是引用类型,则A引用的类型用于类型推导。

执行第 4 点,现在 A 转换为 int const

第 5 条说:

一般来说,推导过程会尝试找到使推导的 A 相同与 A 相同的模板参数值。但是,可能会忽略 A 的某些属性:

  • 如果原始 A 是引用类型,则 A 的任何 cv 限定符(即引用所引用的类型)。
  • 如果原始 A 是函数指针或指向成员函数的指针类型,则为 noexcept。
  • A 中可以通过限定转换恢复的任何 cv 限定符。

这些只有在类型推导失败时才会被忽略

这里有个问题,compiler 说推导的 A 是int,但是,IIUC,从const int 推导出Type,推导的类型难道不是const int?为什么推导出的模板参数是int 而不是const int

如果将转换函数模板改为

#include <iostream>
struct C{
    template<typename Type>
    operator Type&(){
        static Type a{};
        a = 1;
        return a;
    }
};

int main(){
    int const& rf = C{};
}

那么Type将被推导出为const int。为什么此时推导的类型与上面的示例不同? (它们应该是相同的结果,即const int)。在第一个示例中,我从“const int”中推断出Type 有什么遗漏吗?

【问题讨论】:

标签: c++ templates language-lawyer


【解决方案1】:

将候选函数集扩展为指定为允许的类型(用于初始化)不适用于模板转换函数


我不知道A 是否只是这里的int const&amp;或为此初始化定义的任何允许的类型

这里只有[over.match.funcs.general]/7 的第一部分适用:

在每个S 类的转换函数被考虑用于初始化T 类型的对象或引用的情况下,候选函数包括对conversion-function-id 的搜索结果 em> operator T in S.

作为第二部分,涵盖允许的类型,明确排除了模板转换功能 [emphasis mine]:

[...]

每个这样的情况还为显式和非显式转换函数定义了一组允许的类型;每个(非模板)转换函数 [...]

否则,对于通过非模板转换函数进行的初始化,[over.match.conv]/1.1[over.match.ref]/1.1 指定了初始化的允许类型(术语允许的类型是,大部分是由P1787R6添加的)。


[over.match.ref]/1 - 将转换函数的结果绑定到左值引用的直接引用仅考虑产生左值引用的转换函数

为了决定[temp.deduct.conv]/1中的A,我们需要在C类中找到对应的conversion-function-id

[...] 在S 中搜索conversion-function-id operator T 的结果。

由于在这种情况下初始化是直接引用绑定,[over.match.ref]/1 应用 [emphasis mine]:

在 [dcl.init.ref] 中指定的条件下,引用可以直接绑定到将转换函数应用于初始值设定项表达式的结果。 重载解析用于选择要调用的转换函数。假设“reference to cv1 T”是被初始化的引用类型,候选函数选择如下:

  • (1.1) 设R 为一组类型,包括

    • (1.1.1)“对cv2 T2的左值引用”(在初始化左值引用时或对函数的右值引用)和
    • (1.1.2) “cv2 T2”和“对cv2 T2的右值引用”(在初始化右值引用时或对函数的左值引用)

    对于任何T2

非显式转换函数的允许类型是 R 的成员,其中“cv1 T”是引用兼容的 ([dcl.init.ref]) 与“cv2 T2”。 [...]

当我们正在初始化一个左值引用时,只有 (1.1.1)(而不是 (1.1.2))适用,这意味着 R 将是任何 @987654364 的一组类型“对 cv2 T2 的左值引用” @。根据你自己对[temp.deduct.conv]的分析:

生成的候选人将是operator int const() in C,但如上所述,我们不考虑产生“cv2 T2”(int const)的转换函数以供直接参考绑定到左值引用 (int const&amp;)。相反,我们需要创建一个临时的int 并将引用绑定到该临时,这意味着[dcl.init.ref]/5.3 适用(而不是[dcl.init.ref]/5.1)。对于创建一个临时的intAint,P 是Type,这简单地将Type 推导出为int,以及转换的结果,正式地转换的初始化程序, 绑定到左值引用。


如果我们改变模板转换函数返回Type&amp;,一个左值引用,转换函数的结果可能直接绑定到正在初始化的左值引用,根据[over.match]允许.ref]/1:

#include <type_traits>

struct C {
  static constexpr int c{42};
  template <typename Type> 
   operator Type&() { 
     static_assert(std::is_same_v<Type, int const>); 
     return c; 
   }
};

int main() {
  int const& rf = C{};
}

这意味着我们可以将[temp.deduct.conv] 应用于P 作为Type&amp;A 作为int const&amp;,因为不需要创建临时(直接绑定适用);并根据您自己的分析,Type 随后推导出为int const

【讨论】:

  • 嗨,在进行[temp.deduct.conv]/4之前,我们都同意A是int const&,但是,你说A在经历[temp.deduct]之后将是int .conv]/4。我不同意,它说顶级 cv-qualified 将被删除。对于 int const&amp; 类型,这样的 const 限定符不是顶级的,标准在这里有一个 Note。因此,A 将是int const,在我看来 [temp.deduct.conv]/4 的效果只是删除了引用。
  • 第一个问题,我的说法是,请看这个说法:conversion-function-id的conversion-type-id指定的类型正在查找 .据推测,这意味着这些假设的转换函数(当然,它们不是模板),其返回类型在这里用作A。这里的重点是“被仰视”,什么是必须仰视的?当然,它们是由相应上下文指定的那些候选函数。
  • @jackX 关于 2:你是对的,[basic.type.qualifier] 确实清楚地表明 cv 只是 cv T 中的顶级,而不是 cv T&amp;。关于 1:我将其解释为与目标 (operator int const&amp;) 完全匹配的虚构运算符函数,此后 A 最初设置为 int const&amp;。我更加好奇[temp.deduct.conv]/1 中的最后一句是什么意思(“... 是构造...”)。它是由 P1787R6 添加的,但我之前从未见过在这种情况下使用过“构造”一词。有什么想法吗?
  • 我们可以寻呼@DavisHerring,他是野兽 P1787 的作者,它修正了旧标准中的许多模糊之处。他绝对知道为什么。另外,请看basic.lookup#class.member.lookup-7,感觉应该和它有关系。但是,我无法理解“仅当函数模板声明对应于 F 的声明时才考虑将其命名为 t”。由于两个声明可以对应,只是name相同,只是不知道这里的t是什么。
  • @jackX 在阅读了一些之后,我认为这里的关键可能是重载解析限制了在[dcl.init.ref]/5.1[over.match.ref]/1 下考虑直接引用绑定的转换函数,这意味着我们跌倒了回到 [dcl.init.ref]/5.3 和临时物化,用于操作符函数模板不返回左值引用的情况,在这种情况下,推导以 A 作为临时(即 int)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-25
  • 1970-01-01
  • 2020-01-07
  • 1970-01-01
  • 2018-12-05
相关资源
最近更新 更多