代码之家  ›  专栏  ›  技术社区  ›  Vittorio Romeo

使用函数参数作为常量表达式的一部分-gcc与clang

  •  24
  • Vittorio Romeo  · 技术社区  · 8 年前

    请考虑以下代码段:

    template <bool> struct B { };
    
    template <typename T>
    constexpr bool pred(T t) { return true; } 
    
    template <typename T>
    auto f(T t) -> decltype(B<pred(t)>{})
    {
    }
    
    • 叮当声++ (后备箱) 编译代码

    • G+ (后备箱) 编译失败,出现以下错误:

      src:7:34: error: template argument 1 is invalid
      auto f(T t) -> decltype(B<pred(t)>{})
                                      ^
      
      src:7:34: error: template argument 1 is invalid
      src:7:34: error: template argument 1 is invalid
      src:7:34: error: template argument 1 is invalid
      src:7:34: error: template argument 1 is invalid
      src:7:34: error: template argument 1 is invalid
      src:7:25: error: invalid template-id
      auto f(T t) -> decltype(B<pred(t)>{})
                              ^
      
      src:7:36: error: class template argument deduction failed:
      auto f(T t) -> decltype(B<pred(t)>{})
                                          ^
      
      src:7:36: error: no matching function for call to 'B()'
      src:1:24: note: candidate: 'template<bool <anonymous> > B()-> B<<anonymous> >'
      template <bool> struct B { };
                              ^
      
      src:1:24: note:   template argument deduction/substitution failed:
      src:7:36: note:   couldn't deduce template parameter '<anonymous>'
      auto f(T t) -> decltype(B<pred(t)>{})
                                          ^
      

      live example on godbolt.org


    尽管G++的诊断有误导性,但我认为这里的问题是 t 不是 常量表达式 。将代码更改为…

    decltype(B<pred(T{})>{})
    

    …修复了g++上的编译错误: live example on godbolt.org


    什么编译器在这里工作正常?

    3 回复  |  直到 8 年前
        1
  •  4
  •   Richard Smith    7 年前

    GCC是错误的。 没有规则可以阻止以这种方式在常量表达式中使用函数的参数。

    但是,您不能使用 价值 在这样的上下文中的参数,以及一组类型 T 为哪一个 f 可调用是非常有限的。为了了解原因,我们需要考虑在计算表达式时要计算的构造 pred(t) :

    // parameters renamed for clarity
    template <typename U>
    constexpr bool pred(U u) { return true; } 
    
    template <typename T>
    auto f(T t) -> decltype(B<pred(t)>{});
    

    调用的评估语义 预测(t) 如下:

    1. 复制初始化 pred 的参数 u f 的参数 t
    2. 评估的主体 普雷德 这很普通地创造了一个 bool 价值 true
    3. 破坏 U

    所以, f 只能对类型调用 T 上面只涉及常量计算期间有效的构造(请参见 [expr.const]p2 为了规则)。要求如下:

    • T 必须是文本类型
    • 复制的初始化 U T 必须是常量表达式,尤其不能对 T (因为它们的值未知),并且不能命名 T

    实际上,这意味着 f 如果 T 是具有默认复制构造函数的空类类型,或者如果 T 是其复制构造函数为的类类型 constexpr 并且不阅读其论点的任何成员,或者(奇怪地)如果 T std::nullptr_t (尽管 clang currently gets the nullptr_t case wrong )

        2
  •  0
  •   dex black    7 年前

    编译器在该上下文中需要一个参数,因为它需要计算完整(模板重载)函数类型。考虑到pred的实现,任何值都可以在该位置工作。这里它将F参数的模板类型绑定到参数。 g++编译器似乎在做一个简化的假设,即模板 constexpr 函数将以某种方式被任何参数改变,除非它们也 const 正如你所证明的,而且Clang也同意,这不一定是事实。

    归根结底,编译器要在函数实现的深处,将函数标记为非常量,这是由于非常量对返回值的贡献。

    然后还有一个问题,就是函数是否被实例化,需要编译器实际编译代码,而不是执行模板解析,至少使用G++时,这似乎是一个不同的编译级别。

    然后我使用了这个标准,它们允许编译器编写者精确地做出简化假设和模板函数实例化只适用于 f<const T> f <const T&>

    constexpr`函数必须具有:其每个参数都必须 文字类型

    因此,模板代码应该编译,但如果用非常量t实例化,则会失败。

        3
  •  -1
  •   Yankes    7 年前

    t 不是constexpr值,这意味着 pred(t) 也不是constexpr。 你不能用在 B<pred(t)> 因为这需要constexpr。

    此版本正确编译:

    template <bool> struct B { };
    
    template <typename T>
    constexpr bool pred(T t) { return true; } 
    
    template <typename T, T t>
    auto f() -> decltype(B<pred(t)>{})
    {
    }
    

    https://godbolt.org/g/ydbj1X

    另一个有效代码是:

    template <typename T>
    auto f(T t) -> decltype(pred(t))
    {
    }
    

    这是因为你不评估 预测(t) 只有你能得到类型信息。 b<pred(t)> 需要评估 预测(t) 否则你会得到 B<true> B<false> ,对于任何正常值,您都不能这样做。

    std::integral_constant<int, 0>{} 在Clang情况下可以工作可能是因为它的值作为类型的一部分内置,并且始终是相同的。如果我们稍微改变一下代码:

    template <typename T>
    auto f(T t) -> decltype(B<pred(decltype(t){})>{})
    {
        return {};
    }
    

    在这种情况下,clang和gcc都会编译它。 std::integral_constant ,两者 T decltype(t){} 始终具有相同的值。

    推荐文章