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

时间:2018-05-24 14:34:46

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

请考虑以下代码段:

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 ++ (主干)编译代码

  • 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

这里的编译器行为正确吗?

3 个答案:

答案 0 :(得分:4)

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

但是,您不能在这样的上下文中使用参数的 value ,并且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)>{});

呼叫pred(t)的评估语义如下:

  1. pred的参数u复制f的参数t
  2. 评估pred的正文,该正文简单地创建一个booltrue
  3. 破坏u

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

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

实际上,这意味着如果f是具有默认复制构造函数的空类类型,或者T是其复制构造函数为{{ 1}},并且不读取其参数的任何成员,或者(奇怪的是)如果Tconstexpr(尽管clang currently gets the nullptr_t case wrong)。

答案 1 :(得分:0)

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

这一切都归结为函数实现内部有多深,由于非const对返回值的贡献,编译器将函数标记为非const。

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

然后我去了标准,他们恳请编译器编写者完全做出简化假设,模板函数实例化只适用于f<const T>f <const T&>

  

constexpr`函数必须具有:每个参数必须是   LiteralType

因此模板代码应该编译但如果使用非const T进行实例化则会失败。

答案 2 :(得分:-1)

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))
{
}

这是因为仅获得类型信息,您不会评估pred(t)B<pred(t)>需要对pred(t)进行评估,否则您将获得B<true>B<false>,对于任何常规值您都无法做到这一点。

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

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

如果std::integral_constantt始终具有相同的值,则Clang和GCC都会对其进行编译。