代码之家  ›  专栏  ›  技术社区  ›  xmh0511

如何理解#1664的拟议决议

  •  0
  • xmh0511  · 技术社区  · 6 年前

    在研究了#1664的拟议决议后( proposed resolution 1664 ),我对函数模板的默认参数的规则感到困惑,请在这里引用以下内容:

    根据第8.1.5款【解释原始lambda】第3段

    闭包类型在包含 对应的lambda表达式。[注意:这决定了与闭包类型(6.4.2[basic.lookup.argdep])关联的名称空间和类的集合。lambda的参数类型- 声明器不会影响这些关联的名称空间和类。-结束语]

    然而,17.8.1【临时安装】第13段中

    如果函数模板f的调用方式要求使用默认参数,则会查找依赖名称,检查语义约束,并对默认参数中使用的任何模板进行实例化,就像默认参数是在具有相同作用域的函数模板专用化中使用的初始值设定项一样,与当时使用的函数模板f相同的模板参数和访问权限。

    因此,一种可能性是,在模板函数(或者,可能是类模板的成员函数)的默认参数中,lambda表达式的闭包类型被视为已在虚构函数模板专门化的主体中的某个块范围内声明。

    考虑下面的例子:

     namespace J {
        inline namespace K {
          template <typename T> int zap(const T &t) { foo(t); return 0; }
          template <typename T> void zip(int = zap([] { })) { }
        }
        template <typename T> void foo(const T &) { }
      }
      void bar() { 
        J::K::zip<long>(); 
        /*Accroding to the above wording,the invoke just like:  
          => J::K::zip<long>(zap([] { })); 
       */
      }
    

    如果zip不是一个模板,那么依赖参数的查找在所有测试的实现中都成功地解析了foo的查找;然而,在编写的示例处理过程中存在实现差异。

    拟议决议(2013年9月):

    将17.8.1[温度说明]第13段更改如下:
    如果函数模板f的调用方式要求使用默认参数,则会查找依赖名称,检查语义约束,并对默认参数中使用的任何模板进行实例化,就像默认参数是在具有相同作用域的函数模板专用化中使用的初始值设定项一样,与当时使用的函数模板f相同的模板参数和访问权限, 除了声明闭包类型的范围(8.1.5[expr.prim.lambda])以及与其相关的名称空间仍然是根据默认参数定义的上下文确定的 .这种分析称为默认参数实例化。然后将实例化的默认参数用作f的参数。

    注意强调的部分,如果我没有误解,这意味着如果强调的部分注释掉,那么 foo 无法通过 依赖于参数的查找 因为争论 [] { } 哪个名称空间既不是 J 也没有 K ,采用 function bar 喜欢 J::K::zip<long>(zap([] { }) /*default argument*/); ,所以根据 [expr.prim.lambda]第3段 的名称空间 [] { } fuction bar 在这个范围内,没有 可以找到,因此强调的部分是考虑这个名称空间的 [] { } 在内部 zap 一如 扎普 ,它意味着 [] { } K 现在 可以在父命名空间中找到 J 根据依赖于参数的查找规则,到目前为止,如果我误解了这些规则,请纠正我。另一种观点是,每次调用函数时都会计算默认参数,即使默认参数是 不依赖 所以继续考虑下面的代码:

    #include <iostream>
    struct A {
    
    };
    template<typename T>
    int func(T, float) {  //#a
        std::cout << "float" << std::endl;
        return 0;
    }
    template<typename T>
    void test(int = func(A{}, 0)) { //#1
    
    }
    template<typename T>
    int func(T, int) {  //#b
        std::cout << "int" << std::endl;
        return 0;
    }
    int main() {
        test<A>(); //#2 transform to: test<A>(func(A{}, 0)); here,#b should be the best match
        std::getchar();
    }
    

    虽然默认参数 func 是非依赖的,但是每次函数 test 调用,我在一些编译器中测试代码。
    所有版本的MSVC报告“int”,gcc报告“float”,铿锵报告“float”,到底是什么?根据gcc或clang的报告,似乎 func #1 MSVC证明 func 决定于 #2 .如果MSVC是错误的,这意味着非依赖默认参数可以在#1内确定,并且无需确定每次调用函数的时间,为什么需要添加强调部分?( 如果我正确理解了强调的部分,那么它的目的是在默认参数内保持闭包类型的名称空间一致,无论lambda表达式是在函数声明点还是在调用点 ).如果我误解了这些规则,如何正确解释它们?

    更新:

    gcc的9.1或更高版本无法完成#1664中提到的代码,它将报告错误( the complie result )

    问题:

    1.每次调用相应函数时,是否需要确定函数模板或非模板函数的非依赖默认参数?

    2.“默认参数的定义”是什么意思?这个措辞严格吗?( 换句话说,我的理解是,实际上添加的规则想要表达的是closeure类型的名称空间是一个函数declaration的名称空间,其中包含一个包含相应lambda表达式的默认参数,对吗?如果我对此的理解是错误的,请纠正我 )

    0 回复  |  直到 6 年前
        1
  •  3
  •   Davis Herring    6 年前

    默认参数是 评价的 每次调用它们时,这是一个运行时属性:调用不是按源代码行计数,而是按实际控制流计数。另外,模板化函数的默认参数是 considered to be a definition 而且是 实例化 需要时,最多每小时一次 专业化 函数的定义(关于多个实例化点的常规但书必须一致)。基于实例化的方式,CWG1664是一个非常狭窄的问题 措辞 :通过引入 虚拟的 函数模板,它保留了lambdas声明物理移动的可能性。这个修正确实只影响ADL。

    你的 func 该示例演示了模板中常见的名称查找规则:无论查找多少次 从何处 test 将实例化的默认参数, func 在这方面,它不是一个 依靠的 名字,因此发现 func(T,float) (每次)。 MSVC 众所周知,他们从未正确地实现过该规则(公平地说,他们的实现早于该规则,而且他们最近才开始对其模板支持进行必要的(几乎完全的)重写)。

    同时,最近的GCC在CWG1664示例中显然存在缺陷:请注意,它抱怨 foo 正在使用但未定义, 矛盾的 两者都清晰可见 { } 以及之前关于找不到它的错误信息。