不使用它的 constexpr 成员函数?

constexpr member functions that don't use this?

本文关键字:成员 函数 constexpr      更新时间:2023-10-16

请考虑以下两个c++ 14程序:

程序1:

struct S { constexpr int f() const { return 42; } };
S s;
int main() { constexpr int x = s.f(); return x; }

项目2:

struct S { constexpr int f() const { return 42; } };
int g(S s) { constexpr int x = s.f(); return x; }
int main() { S s; return g(s); }

这两个程序都是病态的吗?

为什么/为什么不?

两个程序都是格式良好的。c++ 14标准要求s.f()是一个常量表达式,因为它被用来初始化constexpr变量,事实上,它是一个核心常量表达式,因为没有理由不这样做。表达式可能不是核心常量表达式的原因在第5.19 p2节中列出。特别是,它指出表达式的求值必须做以下几件事中的一件,这些都没有在您的示例中完成。

这可能令人惊讶,因为在某些上下文中,即使不使用参数,将非常量表达式传递给constexpr函数也会导致结果是非常量表达式。例如:
constexpr int f(int) { return 42; }
int main()
{
    int x = 5;
    constexpr auto y = f(x); // ill-formed
}

然而,这是错误格式的原因是因为非常量表达式的左值到右值转换,这是表达式的求值不允许做的事情之一。在调用s.f()的情况下不会发生左值到右值的转换。

我似乎在标准中找不到一个令人信服的段落或例子来直接解决在非constexpr实例上调用constexpr成员函数的问题,但这里有一些可能会有所帮助(来自N4140草案):

[C++14: 7.1.5/5]:

非模板、非默认constexpr函数或非模板、非默认、非继承constexpr构造函数,如果不存在使函数或构造函数调用的实参值可能是核心常量表达式(5.19)的求值子表达式,则程序是病态的;没有诊断需要。

constexpr int f(bool b)
    { return b ? throw 0 : 0; }       // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required

从这里我认为程序不是完全病态的,仅仅因为constexpr函数有一个可能的非constexpr路径。

[C++14: 5.19]:

int x; // not constant
struct A {
    constexpr A(bool b) : m(b?42:x) { }
    int m;
};
constexpr int v = A(true).m; // OK: constructor call initializes
                             // m with the value 42
constexpr int w = A(false).m; // error: initializer for m is
                              // x, which is non-constant

这在某种程度上更接近于您的示例程序,这里constexpr构造函数可以根据参数的值引用非constexpr变量,但如果没有实际采用该路径,则不会出现错误。

所以我不认为你所呈现的程序应该是病态的,但是我不能提供令人信服的证据:)

这听起来像是一个测验问题,而且不是由学生提出的,而是教授在测试公众对stackoverflow的使用,但是让我们看看…

让我们从一个定义规则开始。很明显,两个版本都没有违反这一点,所以它们都通过了这一部分。

然后,到语法。两者都没有语法错误,如果您不介意潜在的语法和语义混合问题,它们都可以顺利编译。

首先是更简单的语义问题。这不是语法问题,但在两个版本中,f()都是结构体的成员,而且该函数显然没有改变所属结构体,它返回一个常量。虽然该函数声明为constexpr,但没有声明为const,这意味着如果有某种理由将其作为运行时函数调用,如果在const s上进行调用,则会产生错误。这将影响两个版本。

现在,潜在的模棱两可的return g(S());很明显,外面的g是一个函数调用,但是S可能不像写return g(S{});那样清晰,如果用{}初始化S,将来struct S用操作符()展开就不会有模棱两可了(结构体已经很像一个函子了)。现在调用的构造函数是自动生成的,并且在这个版本中没有操作符()会给编译器造成混淆,但现代c++ 14应该提供更清晰的替代方案,以避免类似于g(S())的"最令人烦恼的解析"。

所以,我不得不说,基于语义规则,它们都失败了(虽然不是那么严重)。