c++ - 为什么 constexpr 函数中有 "never use non-literal type"规则?

标签 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
}
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 :(
}
https://godbolt.org/z/iHThCq
这背后的原因是什么?为什么使用 std::string 是非法的,即使它实际上从未被构造(或销毁)?
额外问题:为什么以下合法:https://godbolt.org/z/L3np-u (上面略有变化,没有定义std::string)?!

最佳答案

我只是在这里猜测,但这可能是因为 std::string s = "abc"一个自动变量并在函数开始时分配在堆栈中(即使尚未构造)会破坏 constexpr规则?
如果我将代码更改为:

using namespace std::string_literals;

bool bar(std::string);

template <class T>
constexpr bool foo(T t) {
    if (t>0) {
        return true;
    }
    else {
        //std::string ss = "abc"s;
        return bar("abc"s);
    }
    return false;
}
因为不需要分配它编译的任何东西。
我在这里解释我的推理(以及对评论的回应),因为我需要比评论更多的空间。
正如@StoryTeller-UnslanderMonica 所说,“猜测是回答问题的糟糕基础”。
绝对没错。这就是为什么我开始这么说的原因:我猜。这是有原因的。
我不喜欢正常猜测,但我发现这很有趣,想想想是否有人说我错了(我已经准备好接受了。)
但说到重点,文字类型变量通常存储在一些只读内存数据段中(除非它们是数字,那些可以直接转换为 ASM MOV/... 指令),而不是在堆栈中。
如果声明为自动(存储在堆栈中):

Storage duration

All objects in a program have one of the following storage durations:

automatic storage duration. The storage for the object is allocated at the beginning of the enclosing code block and deallocated at the end. All local objects have this storage duration, except those declared static, extern or thread_local.


(强调我的。)
因此,即使在 if 之后声明, 存储已分配并且在任何情况下都应该被释放(在 OP 显示的示例中。)
事实上,如果这样做:
template <class T>
constexpr bool foo(T t) {
    if (t>0) {
        return true;
    }
    const std::string ss = "abc"s;
    return bar(ss);
}
错误是:
main.cc:15:16: error: call to non-‘constexpr’ function ‘std::__cxx11::basic_string<char> std::literals::string_literals::operator""s(const char*, std::size_t)’
为什么?我猜是因为,无论执行代码路径如何,“对象的存储都是在封闭代码块的开头分配的”(函数的开头)是自动的。
此外,如果您声明它 constexpr ,它引入了析构函数:
template <class T>
constexpr bool foo(T t) {
    if (t>0) {
        return true;
    }
    constexpr std::string ss = "abc"s;
    return bar(ss);
}
错误:
main.cc:19:32: error: temporary of non-literal type ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’} in a constant expression
     constexpr std::string ss = "abc"s;
                                ^~~~~~
In file included from /usr/include/c++/8/string:52,
                 from main.cc:2:
/usr/include/c++/8/bits/basic_string.h:77:11: note: ‘std::__cxx11::basic_string<char>’ is not literal because:
     class basic_string
           ^~~~~~~~~~~~
/usr/include/c++/8/bits/basic_string.h:77:11: note:   ‘std::__cxx11::basic_string<char>’ has a non-trivial destructor
main.cc: In instantiation of ‘constexpr bool foo(T) [with T = int]’:
main.cc:25:29:   required from here
main.cc:19:27: error: the type ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’} of ‘constexpr’ variable ‘ss’ is not literal
     constexpr std::string ss = "abc"s;
我认为关键是:‘std::__cxx11::basic_string<char>’ has a non-trivial destructor .
因此在执行代码路径之前考虑了对析构函数的理论调用。
为什么?
因为“对象的存储空间是在封闭代码块的开头分配的”。
以下:
template <class T>
constexpr bool foo(T t) {
    if (t>0) {
        return true;
    }
    return bar("abc"s);
}
创建一个临时的:
main.cc:19:15: error: call to non-‘constexpr’ function ‘bool bar(std::__cxx11::string)’
     return bar("abc"s);
template <class T>
constexpr bool foo(T t) {
    if (t>0) {
        return true;
    } else {
        return bar("abc"s);
    }
    return false;
}
仅当执行路径转到 else 时才创建临时(事实并非如此。)
如前所述,这是一个猜测,但我认为是基于猜测,而不仅仅是盲目尝试。
同样,我确信这取决于编译器的实现。我绝不是 C++ 标准专家,但我无法在任何文档中找到这个明确的案例。
我已经在 gdb 中运行了程序查看是否进入foo功能:
bool bar(std::string);

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

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

    cout << "Bool: " << cb2 << endl;
    
    return 0;
}
它链接没有 bar如此定义……
manuel@desktop:~/projects$ g++ -Wall -Wextra -g main.cc -o main --std=gnu++2a -Wpedantic && time ./main
Bool: 1

real    0m0,002s
user    0m0,000s
sys 0m0,002s
manuel@desktop:~/projects$ gdb ./main
GNU gdb (Debian 8.2.1-2+b3) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./main...done.
(gdb) b main
Breakpoint 1 at 0x117d: file main.cc, line 27.
(gdb) r
Starting program: /home/manuel/projects/main 

Breakpoint 1, main () at main.cc:27
27      constexpr bool cb2 = foo(1); // this is also an error now :(
(gdb) s
29      cout << "Bool: " << cb2 << endl;
(gdb) s
Bool: 1
31      return 0;
(gdb) s
32  }
(gdb) q
A debugging session is active.

    Inferior 1 [process 18799] will be killed.

Quit anyway? (y or n) y

关于c++ - 为什么 constexpr 函数中有 "never use non-literal type"规则?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62782798/

相关文章:

c++ - GCC 错误 - 在 constexpr 中输入/递减数组访问

C++ - 将 decltype 与包含在模板类中的枚举一起使用

c++ - 当迭代器的 vector 被添加到时,如何保持迭代器可取消引用?

C++:我如何 "store"节点

c++ - 将顶点位置从顶点传递到片段着色器 - 仅在使用 Nsight 调试时有效

c++ - 销毁顺序和赋值

构造函数中的 C++14 constexpr union 条件初始化

c++ - 如何在其值可用于常量表达式的类中初始化数组?

c++ - 如何将 constexpr 函数的参数标记为未使用?

c++ - 在编译时常量中为字符串文字添加下标