不使用这个的 constexpr 成员函数?
constexpr member functions that don't use this?
请考虑以下两个 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); }
这些程序中的一个或两个都不是格式错误的吗?
Why/why不是吗?
这听起来像是一个测验问题,不是学生提出的,而是教授在 Whosebug 上测试 public,但让我们看看...
让我们从一个定义规则开始。很明显两个版本都没有违反这一点,所以他们都通过了那部分。
然后,到了语法。两者都没有语法错误,如果您不介意语法和语义问题的潜在混合,它们都可以毫无问题地编译。
首先,更简单的语义问题。这不是语法问题,但 f()
在两个版本中都是结构的成员,并且该函数显然没有对拥有的结构进行任何更改,它返回一个常量。尽管该函数被声明为 constexpr,但它并未声明为 const,这意味着如果有某种原因将其作为运行时函数调用,那么如果该尝试是在 const S 上进行的,则会产生错误。这会影响两个版本。
现在,潜在的歧义 return g(S());
很明显外层 g 是一个函数调用,但是 S 可能不像写的那么清楚 return g(S{});
使用 {} 初始化 S,会有毫无疑问,将来 struct S
应该用 operator() 扩展(该结构已经几乎类似于仿函数)。调用的构造函数现在自动生成,并且没有 operator() 在此版本中为编译器造成混淆,但现代 C++14 应该提供更清晰的替代方案来避免 "Most Vexing Parse",其中 g(S ()) 类似。
所以,我不得不说,基于语义规则,它们都失败了(虽然不是那么糟糕)。
两个程序的格式都正确。 C++14 标准要求 s.f()
是一个常量表达式,因为它被用来初始化一个 constexpr
变量,事实上它是一个核心常量表达式,因为没有理由不这样做.表达式可能不是核心常量表达式的原因在第 5.19 节 p2 中列出。特别是,它指出表达式的 evaluation 必须做几件事之一,none 在您的示例中已完成。
这可能令人惊讶,因为在某些情况下,将非常量表达式传递给 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]
:
For a non-template, non-defaulted constexpr
function or a non-template, non-defaulted, non-inheriting
constexpr
constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no
diagnostic required.
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 变量,但如果实际上没有采用此路径,则不会出错。
所以我认为您提供的任何一个程序都不应该是格式错误的,但我无法提供令人信服的证据:)
请考虑以下两个 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); }
这些程序中的一个或两个都不是格式错误的吗?
Why/why不是吗?
这听起来像是一个测验问题,不是学生提出的,而是教授在 Whosebug 上测试 public,但让我们看看...
让我们从一个定义规则开始。很明显两个版本都没有违反这一点,所以他们都通过了那部分。
然后,到了语法。两者都没有语法错误,如果您不介意语法和语义问题的潜在混合,它们都可以毫无问题地编译。
首先,更简单的语义问题。这不是语法问题,但 f()
在两个版本中都是结构的成员,并且该函数显然没有对拥有的结构进行任何更改,它返回一个常量。尽管该函数被声明为 constexpr,但它并未声明为 const,这意味着如果有某种原因将其作为运行时函数调用,那么如果该尝试是在 const S 上进行的,则会产生错误。这会影响两个版本。
现在,潜在的歧义 return g(S());
很明显外层 g 是一个函数调用,但是 S 可能不像写的那么清楚 return g(S{});
使用 {} 初始化 S,会有毫无疑问,将来 struct S
应该用 operator() 扩展(该结构已经几乎类似于仿函数)。调用的构造函数现在自动生成,并且没有 operator() 在此版本中为编译器造成混淆,但现代 C++14 应该提供更清晰的替代方案来避免 "Most Vexing Parse",其中 g(S ()) 类似。
所以,我不得不说,基于语义规则,它们都失败了(虽然不是那么糟糕)。
两个程序的格式都正确。 C++14 标准要求 s.f()
是一个常量表达式,因为它被用来初始化一个 constexpr
变量,事实上它是一个核心常量表达式,因为没有理由不这样做.表达式可能不是核心常量表达式的原因在第 5.19 节 p2 中列出。特别是,它指出表达式的 evaluation 必须做几件事之一,none 在您的示例中已完成。
这可能令人惊讶,因为在某些情况下,将非常量表达式传递给 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]
:
For a non-template, non-defaulted
constexpr
function or a non-template, non-defaulted, non-inheritingconstexpr
constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.
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 变量,但如果实际上没有采用此路径,则不会出错。
所以我认为您提供的任何一个程序都不应该是格式错误的,但我无法提供令人信服的证据:)