【问题标题】:Should this be a constexpr or not?这应该是 constexpr 还是不是?
【发布时间】:2020-11-30 05:19:20
【问题描述】:

考虑一下这段代码 sn-p (godbolt):

#include <cstdio>
#include <string>
#include <string_view>

struct Option
{
    std::string_view name;

    constexpr Option( std::string_view const n ) noexcept : name{n} {}
};

template< std::size_t N >
class TransformedOption : public Option
{
public:
    constexpr TransformedOption( std::string_view const nameStr ) :
        Option{ { nameStorage_, N - 1 } }
    {
        for ( auto i = 0U; i < N; ++i )
        {
            if ( nameStr[ i ] == '_' ) { nameStorage_[ i ] = '-'; }
            else                       { nameStorage_[ i ] = nameStr[ i ]; }
        }
    }
    private:
        char nameStorage_[ N ] = {};
};

template< std::size_t N >
constexpr TransformedOption< N > make( char const (&nameStr)[ N ] ) noexcept
{
    return TransformedOption< N  >{ nameStr };
}

int main()
{
    /*constexpr*/ auto t = make( "abcd_efgh_ijkl_mnop_peqst" );
    std::printf( "%s\n", t.name.data() );
    return 0;
}

基本上,我想通过将每个_ 替换为- 来执行编译时字符串转换,并确保最终的二进制文件只包含转换后的字符串(而不是原始字符串)。

我已经尝试过 Clang 10.0.1、GCC 10.2 和 MSVC 19.24(请参阅上面的 gobolt 链接)。奇怪的东西如下:

  • 如果constexprmain 中被注释掉,那么MSVC 会生成不正确的代码(即字符串的运行时转换),但GCC 和clang 都会生成正确的代码(即转换后的字符串常量嵌入到程序集中)李>
  • 如果constexpr 没有在main 中被注释掉,那么MSVC 会生成正确的代码(即转换后的字符串常量嵌入到程序集中),但是GCC 和clang 都无法编译代码,说明t不是由常量表达式初始化的(参见godbolt)。最奇怪的是 GCC 错误消息,它在错误中输出 transformed 字符串并声明它不是常量表达式。

那么,根据 C++ 标准,哪个编译器是正确的?我应该向谁报告错误?对 GCC 和 Clang 人员还是对 Microsoft?

【问题讨论】:

  • "MSVC 生成不正确的代码(即字符串的运行时转换)" 那是正确的代码。除非有必要,否则编译器不需要在编译时做任何事情。 constexpr on functions 是一个选项,除非在需要常量表达式的上下文中调用。
  • 有趣的是,一旦你摆脱了Option 基类(并根据需要通过函数调用生成string_view),the code works just fine on all compilers。因此,基类中可能发生了一些让编译器感到困惑的事情。
  • @NicolBolas,我知道这是正确的代码。不过,这对我的事业来说是不正确的(即,这不是我想要实现的目标)。

标签: c++ constexpr c++20


【解决方案1】:

t 也被声明为静态时,constexpr 声明适用于所有编译器。

constexpr static auto t = make( "abcd_efgh_ijkl_mnop_peqst" );

原因是string_view。它是一种引用类型,指向正在初始化的对象。因此,以一种或另一种方式,您正在初始化一个 contexpr 指针。现在,constexpr 指针(未初始化为空指针)只能使用具有静态存储持续时间的对象的地址进行初始化。

[expr.const](强调我的)

11 常量表达式要么是左值核心常量 引用实体的表达式,该实体是 a 的允许结果 常量表达式(定义如下),或纯右值核心常量 其值满足以下约束的表达式:

  • 如果值是类类型的对象,则每个引用类型的非静态数据成员引用一个允许的实体 常量表达式的结果,
  • 如果值是指针类型,它包含一个具有静态存储期的对象的地址,该地址超过了这样的结尾 一个对象([expr.add]),一个非立即函数的地址,或者一个 空指针值,
  • 如果值是指向成员函数类型的,它不指定立即函数,并且
  • 如果该值是类或数组类型的对象,则每个子对象都满足该值的这些约束。

一个实体是一个常量表达式的允许结果,如果它是一个 具有静态存储持续时间且不是临时的对象 对象或者是一个临时对象,其值满足上述条件 约束,或者如果它是一个非立即函数。

当您将对象声明为具有自动存储持续时间时,string_view 中的指针不会使用静态对象的地址进行初始化。因此 GCC 和 Clang 有理由抱怨。

自我引用使这变得有趣和棘手。

【讨论】:

  • 具体规则为:eel.is/c++draft/expr.const#11
  • 谢谢!所以,我猜,这是 MSVC 中的一个错误——它也应该报告一个错误?此外,如果 GCC 和 clang 都打印出更有用的错误消息(例如,引用您在此处引用的规则),那就太好了。
  • @DoDo - 是的,MSVC 接受自动 constexpr 变量肯定是错误的。就 Clang 和 GCC 的 QoI 而言,可能有一些选项可以让他们在持续评估中遇到问题的地方打印出来。但是,我不记得它们。
  • @DoDo Clang 实际上会告诉您问题(“指向 't' 的子对象的指针不是常量表达式”),尽管如果它也为您提供调用堆栈会很棒。已将96557 提交给 gcc。
猜你喜欢
  • 1970-01-01
  • 2014-11-06
  • 1970-01-01
  • 2013-06-22
  • 2012-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多