为什么在 constexpr 函数中有“从不使用非文字类型”规则?

Nic*_*ick 32 c++ constexpr

取以下合法代码:

bool bar();

template <class T>
constexpr bool foo(T t) {
  if (t>0) {
    return true;
  }
  return bar();
}


int main() {
  //constexpr bool cb1 = foo(-1); // error as expected  because it would attempt to call bar()
  constexpr bool cb2 = foo(1); // ok
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/UWt_3A

因此,只要我们没有在编译时评估上下文中遇到非 constexpr 代码路径,我们的 constexpr 就形成了良好的格式。整洁的!

但是,如果我应用相同的实际概念,但碰巧在条件代码路径中包含非文字类型,例如std::string,则标准说不:

#include <string>

bool bar(std::string);

template <class T>
constexpr bool foo(T t) {
  if (t>0) {
    return true;
  }
  std::string s = "abc";
  return bar(s);
}


int main() {
  //constexpr bool cb1 = foo(-1); // error as expected
  constexpr bool cb2 = foo(1); // this is also an error now :(
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/iHThCq

这背后的原理是什么?为什么不惜一切代价使用 std::string 是非法的,即使它实际上从未被构造(或销毁)?

额外问题:那么为什么以下合法:https : //godbolt.org/z/L3np-u (上面略有变化,没有定义 std::string)?!

Man*_*uel 4

我只是在这里猜测,但这是否是因为作为std::string s = "abc"一个自动变量并在函数开始时在堆栈中分配(即使尚未构造)违反了规则constexpr

\n

如果我将代码更改为:

\n
using namespace std::string_literals;\n\nbool bar(std::string);\n\ntemplate <class T>\nconstexpr bool foo(T t) {\n    if (t>0) {\n        return true;\n    }\n    else {\n        //std::string ss = "abc"s;\n        return bar("abc"s);\n    }\n    return false;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

因为不需要分配它编译的任何东西。

\n

我在这里解释我的推理(以及对评论的回应),因为我需要比评论更多的空间。

\n

正如@StoryTeller-UnslanderMonica 所说,“猜测是回答问题的糟糕基础”。

\n

绝对没错。这就是为什么我开始这么说:我猜测。这是有原因的。

\n

我通常不喜欢猜测,但我发现这很有趣,并且想思考一下是否有人说我错了(我已经准备好接受这一点。)

\n

但说到重点,文字类型变量通常存储在某些只读存储器数据段中(除非它们是数字,否则它们可以直接转换为 ASM MOV/... 指令),而不是存储在堆栈中。

\n

如果声明为自动(存储在堆栈中):

\n
\n

储存期限

\n

程序中的所有对象都具有以下存储持续时间之一:

\n

自动存储时间。对象的存储在封闭代码块的开头分配,并在末尾释放。所有本地对象都有这个存储持续时间,除了那些声明为 static、extern 或 thread_local 的对象。

\n
\n

(强调我的。)

\n

因此,即使在 后声明if,存储也会被分配,并且在任何情况下都应该被释放(在 OP 所示的示例中)。

\n

事实上,如果这样做:

\n
template <class T>\nconstexpr bool foo(T t) {\n    if (t>0) {\n        return true;\n    }\n    const std::string ss = "abc"s;\n    return bar(ss);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

错误是:

\n
main.cc:15:16: error: call to non-\xe2\x80\x98constexpr\xe2\x80\x99 function \xe2\x80\x98std::__cxx11::basic_string<char> std::literals::string_literals::operator""s(const char*, std::size_t)\xe2\x80\x99\n
Run Code Online (Sandbox Code Playgroud)\n

为什么?我想是因为,无论执行代码路径如何,“对象的存储都是自动分配在封闭代码块的开头”(函数的开头)。

\n

此外,如果你声明它constexpr,它会引入析构函数:

\n
template <class T>\nconstexpr bool foo(T t) {\n    if (t>0) {\n        return true;\n    }\n    constexpr std::string ss = "abc"s;\n    return bar(ss);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

错误:

\n
main.cc:19:32: error: temporary of non-literal type \xe2\x80\x98const string\xe2\x80\x99 {aka \xe2\x80\x98const std::__cxx11::basic_string<char>\xe2\x80\x99} in a constant expression\n     constexpr std::string ss = "abc"s;\n                                ^~~~~~\nIn file included from /usr/include/c++/8/string:52,\n                 from main.cc:2:\n/usr/include/c++/8/bits/basic_string.h:77:11: note: \xe2\x80\x98std::__cxx11::basic_string<char>\xe2\x80\x99 is not literal because:\n     class basic_string\n           ^~~~~~~~~~~~\n/usr/include/c++/8/bits/basic_string.h:77:11: note:   \xe2\x80\x98std::__cxx11::basic_string<char>\xe2\x80\x99 has a non-trivial destructor\nmain.cc: In instantiation of \xe2\x80\x98constexpr bool foo(T) [with T = int]\xe2\x80\x99:\nmain.cc:25:29:   required from here\nmain.cc:19:27: error: the type \xe2\x80\x98const string\xe2\x80\x99 {aka \xe2\x80\x98const std::__cxx11::basic_string<char>\xe2\x80\x99} of \xe2\x80\x98constexpr\xe2\x80\x99 variable \xe2\x80\x98ss\xe2\x80\x99 is not literal\n     constexpr std::string ss = "abc"s;\n
Run Code Online (Sandbox Code Playgroud)\n

我认为关键是:\xe2\x80\x98std::__cxx11::basic_string<char>\xe2\x80\x99 has a non-trivial destructor

\n

因此理论上对析构函数的调用是在执行代码路径之前考虑的。

\n

为什么?

\n

因为“对象的存储是在封闭代码块的开头分配的”。

\n

下列:

\n
template <class T>\nconstexpr bool foo(T t) {\n    if (t>0) {\n        return true;\n    }\n    return bar("abc"s);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

创建一个临时的:

\n
main.cc:19:15: error: call to non-\xe2\x80\x98constexpr\xe2\x80\x99 function \xe2\x80\x98bool bar(std::__cxx11::string)\xe2\x80\x99\n     return bar("abc"s);\n
Run Code Online (Sandbox Code Playgroud)\n

\n
template <class T>\nconstexpr bool foo(T t) {\n    if (t>0) {\n        return true;\n    } else {\n        return bar("abc"s);\n    }\n    return false;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当执行路径转到else(情况并非如此)时才创建临时文件。

\n

正如我所说,这是一个猜测,但我认为这是一个有基础的猜测,而不仅仅是盲目的尝试。

\n

再次,我确信这取决于编译器的实现。我绝不是 C++ 标准专家,但我在任何文档中都找不到这种明确的案例。

\n

我已经运行该程序以gdb查看它是否进入该foo函数:

\n
bool bar(std::string);\n\ntemplate <class T>\nconstexpr bool foo(T t) {\n    if (t>0) {\n        return true;\n    } else {\n        //std::string ss = "abc"s;\n        return bar("abc"s);\n    }\n    return false;\n}\n\nint main() {\n    //constexpr bool cb1 = foo(-1); // error as expected\n    constexpr bool cb2 = foo(1); // this is also an error now :(\n\n    cout << "Bool: " << cb2 << endl;\n    \n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它链接没有bar被定义所以......

\n
manuel@desktop:~/projects$ g++ -Wall -Wextra -g main.cc -o main --std=gnu++2a -Wpedantic && time ./main\nBool: 1\n\nreal    0m0,002s\nuser    0m0,000s\nsys 0m0,002s\nmanuel@desktop:~/projects$ gdb ./main\nGNU gdb (Debian 8.2.1-2+b3) 8.2.1\nCopyright (C) 2018 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\nType "show copying" and "show warranty" for details.\nThis GDB was configured as "x86_64-linux-gnu".\nType "show configuration" for configuration details.\nFor bug reporting instructions, please see:\n<http://www.gnu.org/software/gdb/bugs/>.\nFind the GDB manual and other documentation resources online at:\n    <http://www.gnu.org/software/gdb/documentation/>.\n\nFor help, type "help".\nType "apropos word" to search for commands related to "word"...\nReading symbols from ./main...done.\n(gdb) b main\nBreakpoint 1 at 0x117d: file main.cc, line 27.\n(gdb) r\nStarting program: /home/manuel/projects/main \n\nBreakpoint 1, main () at main.cc:27\n27      constexpr bool cb2 = foo(1); // this is also an error now :(\n(gdb) s\n29      cout << "Bool: " << cb2 << endl;\n(gdb) s\nBool: 1\n31      return 0;\n(gdb) s\n32  }\n(gdb) q\nA debugging session is active.\n\n    Inferior 1 [process 18799] will be killed.\n\nQuit anyway? (y or n) y\n
Run Code Online (Sandbox Code Playgroud)\n

  • 为什么要删除 else 块?当字符串变量位于该块中时,会发生相同的错误。我想你必须这样做,这样你的解释才会看起来合理。存储时间与此无关。 (2认同)