c++ - 如何理解#1664的提议决议

标签 c++ templates c++17 language-lawyer

看了 #1664( proposed resolution 1664 ) 的建议解决方案后,我对函数模板的默认参数的规则感到困惑,在此处引用内容:

根据 8.1.5 [expr.prim.lambda] 第 3 段

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [Note: This determines the set of namespaces and classes associated with the closure type (6.4.2 [basic.lookup.argdep]). The parameter types of a lambda- declarator do not affect these associated namespaces and classes. —end note]



但是,17.8.1 [temp.inst] 第 13 段说

If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point.



那么,一种可能性是,模板函数(或者,可能是类模板的成员函数)的默认参数中 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([] { })); 
   */
  }

If zip were not a template, argument-dependent lookup successfully resolves the lookup for foo in all implementations tested; however, there is implementation variance in the handling of the example as written.



提议的决议(2013 年 9 月):

Change 17.8.1 [temp.inst] paragraph 13 as follows:
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared (8.1.5 [expr.prim.lambda]) — and therefore its associated namespaces — remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.



注意强调的部分,如果我没有误解,就是说如果强调的部分注释掉,foo无法通过 查找参数依赖查找 因为参数 [] { }哪个命名空间既不是 J也不是 K ,假设形式为 function bar喜欢 J::K::zip<long>(zap([] { }) /*default argument*/); ,所以根据 [expr.prim.lambda] 第 3 段 [] { } 的命名空间在 fuction bar在那个范围内,没有 foo可以发现,所以强调的部分是针对这种考虑[] { }的命名空间的案例。内zapzap ,表示[] { }的命名空间是 K ,现在foo可以在父命名空间 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”,clang报告“float”,这是什么鬼?根据gcc或clang的报告,似乎是func确定在 #1和 MSVC 证明了 func确定在 #2 .如果 MSVC 错误,这意味着可以在 #1 内确定非依赖的默认参数,不需要每次调用函数时都确定,为什么要添加强调部分?( 如果我正确理解强调部分,其目的是保持闭包类型的命名空间在默认参数中保持一致,无论 lambda 表达式是在函数声明点还是调用点 )。如果我误解了这些规则,如何正确解释它们?

更新:

gcc 9.1以上版本无法编译#1664中的代码,会报错(the complie result)

问题:

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

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

最佳答案

默认参数是 评价 每次调用它们时,但这是一个运行时属性:调用不是按源代码行而是按实际控制流计数。另外,模板化函数的默认参数是 considered to be a definition并且是 实例化 需要时,每个 最多一次专业函数的(带有关于必须同意的多个实例化点的通常条件)。 CWG1664 是一个非常狭窄的问题,基于实例化的措辞:通过引入 虚构 函数模板,它留下了 lambda 声明“物理”移动的可能性。该修复确实只影响 ADL。

您的 func示例反而说明了模板中通常的名称查找规则:无论多少次和从哪里test的默认参数被实例化,func在它不是受抚养人 name 并因此找到 func(T,float) (每次)。 MSVC 众所周知,该规则从未正确实现(因为公平地说,他们的实现早于该规则,而且他们最近才开始对其模板支持进行必要的(并且几乎完全)重写)。

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

关于c++ - 如何理解#1664的提议决议,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60691059/

相关文章:

C++ 多线程 : keep only one thread (other than the main thread) alive at a time

c++ - 创建模板参数的默认值变量

c++ - 使用 std::atomic 标志和 std::condition_variable 等待工作线程

c++ - 使用 C++17 是否可以检测结构/类是否有任何基础?

c++ - 生成 boost::hana::set 的常量表达式问题

c++ - 给定一个模板参数,如何确定这是否实际上是一个参数化模板类?

c++ - GTK+ 如何处理指针?

c++ - 除以余数时,奇数是否总是返回 floor?

c++ - 候选模板被忽略 : substitution failure(error with clang but not g++)

c++ - 混合模板和非模板访问者方法