【问题标题】:Using function argument as part of a constant expression - gcc vs clang使用函数参数作为常量表达式的一部分 - gcc vs clang
【发布时间】:2018-11-03 20:08:35
【问题描述】:

考虑以下代码sn-p:

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)>{})
{
}
  • clang++ (trunk) 编译代码

  • g++ (trunk) 编译失败并出现以下错误:

    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


什么编译器在这里表现正确?

【问题讨论】:

  • 为什么你的f函数里没有return语句?
  • @YSC 不要吹毛求疵 :)
  • 如果你真的实例化fclang也会抱怨。
  • @Rakete1111 我也没有,但f(std::integral_constant&lt;int, 0&gt;{})clang 接受...
  • 我认为您的问题与以下内容非常相关:auto f(std::integral_constant&lt;int, 0&gt; t) -&gt; std::integral_constant&lt;int, t&gt;; 有效吗?”(即,在某些情况下,您可以将函数参数用作常量表达式)。 .clang 接受它,gcc 不接受。如果这是有效的,这意味着您的模板对某些T 有效,因此clang 不抛出错误是正确的,否则,gcc 是正确的。

标签: c++ c++11 language-lawyer constexpr constant-expression


【解决方案1】:

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

但是,您不能在这样的上下文中使用参数的,并且f 可调用的类型T 的集合非常有限。要了解原因,我们需要考虑在评估表达式 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)>{});

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

  1. f的参数t复制初始化pred的参数u
  2. 评估pred 的主体,这很容易创建booltrue
  3. 摧毁u

因此,f 仅可调用类型 T,其中上述仅涉及在常量评估期间有效的构造(有关规则,请参阅 [expr.const]p2)。要求是:

  • T 必须是文字类型
  • t 复制初始化u 必须是一个常量表达式,尤其不能对t 的任何成员执行左值到右值转换(因为它们的值未知),并且不得命名t 的任何参考成员

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

【讨论】:

    【解决方案2】:

    编译器在该上下文中需要一个参数,因为它需要评估完整的(模板重载)函数类型。鉴于 pred 的实现,任何值都可以在该位置工作。在这里,它将 f 参数的模板类型绑定到参数。 g++ 编译器似乎做了一个简化的假设,即模板constexpr 函数将以某种方式被任何参数更改,除非它们也是const,正如您所展示的那样,clang 同意,不一定是这种情况。

    这一切都归结为由于非常量对返回值的贡献,编译器将函数标记为非常量的函数实现的深度。

    然后是函数是否被实例化并要求编译器实际编译代码与执行模板解析的问题,至少在 g++ 中,这似乎是不同级别的编译。

    然后我去了标准,他们允许编译器编写者准确地做出简化假设,模板函数实例化应该只适用于 f&lt;const T&gt;f &lt;const T&amp;&gt;

    constexpr` 函数必须有:它的每个参数必须是 文字类型

    所以模板代码应该可以编译,但如果用非常量 T 实例化就会失败。

    【讨论】:

    • 这些只是基于经验的高级概括。我通常不会在 C++ 编译器解析器中四处寻找。这可能对一个人的健康有害。 ;)
    • 我很想尝试在 remove_const 中混合,但我认为我已经深入研究了......
    【解决方案3】:

    t 不是 constexpr 值,这意味着 pred(t) 也不是 constexpr。 您不能在 B&lt;pred(t)&gt; 中使用它,因为这需要 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))
    {
    }
    

    这是因为您不评估 pred(t) 只有您获得类型信息。 B&lt;pred(t)&gt; 需要对 pred(t) 进行评估,否则您将获得 B&lt;true&gt;B&lt;false&gt;,对于任何正常值,您都无法做到这一点。

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

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

    Clang 和 GCC 都会编译它,以防 std::integral_constanttdecltype(t){} 始终具有相同的值。

    【讨论】:

    • t 的值可能无法计算。你可以看到this example
    • @xskxzr 我补充一点来回答你的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-21
    • 1970-01-01
    • 1970-01-01
    • 2017-08-21
    • 1970-01-01
    • 2013-08-21
    • 2018-02-08
    相关资源
    最近更新 更多