static_assert 在使用 constinit const 时失败。 constinit, constinit const, constexpr, const, nonconst 变量混淆

static_assert fails while using constinit const. Confusion in constinit, constinit const, constexpr, const, nonconst variables

我有一个关于编译时函数的问题。我知道 static_assert 应该只适用于类型,在编译时可以是 evaluated/computed 。所以它不适用于 std::string (然而,在 gcc10 中不支持 constexpr std::string)但可以与 std::array 一起使用(当我在编译时知道大小时)。我正在观看 Jason Turner 的 C++ Weekly,所以这段片段来自这一集 https://www.youtube.com/watch?v=INn3xa4pMfg

代码在这里:https://godbolt.org/z/e3WPTP

#include <array>
#include <algorithm>

template<typename Key, typename Value, std::size_t Size>
struct Map final
{
    std::array<std::pair<Key, Value>, Size> _data;

    [[nodiscard]] constexpr Value getMappedKey(const Key& aKey) const
    {
        const auto mapIterator = std::ranges::find_if(_data, [&aKey](const auto& pair){ return pair.first == aKey;});

        if(mapIterator != _data.end())
        {
            return mapIterator->second;
        }
        else
        {
            throw std::out_of_range("Key is not in the map");
        }
    }
};

enum class OurEnum
{
    OUR_VALUE,
    OUR_VALUE2,
    OUR_VALUE3
};

enum class TheirEnum
{
    THEIR_VALUE,
    THEIR_VALUE2,
    THEIR_VALUE3
};


// This Fails non constant variable of course
/*
    Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };
*/

// This fails, it is const, but this does not guarentee that it will be created in compile time
/*
    const Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };
*/

// This works
/*
    constexpr Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };
*/

//How come this does not work? Oh i see, missing const because constinit does not apply constness
/*
    constinit Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };
*/

// Okay, I added const specifier but still this makes static_assert fail because of non-constant condition
// Why?

    constinit const Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };


int main() 
{
    static_assert(enumsConverter.getMappedKey(OurEnum::OUR_VALUE) == TheirEnum::THEIR_VALUE);
}

我正在玩这个示例,发现 static_assert 不适用于 constinit const 初始化映射。我把每一种可能性都注释掉了,我想解释一下。

  1. Map 被初始化为 非常量 变量。我知道这是行不通的,它不是常量变量,也不是编译时初始化的
  2. Map 被初始化为 const 变量。这也不行,即使变量是常量,也不能保证在编译时创建。
  3. Map 被初始化为 constexpr 变量。这保证变量将在编译时初始化。它还意味着常量,所以我们有编译时常量变量。这工作正常。 (https://en.cppreference.com/w/cpp/language/constexpr)
  4. 地图被初始化为constinit变量。现在,constinit 保证表达式是零初始化的或常量初始化的。我用常量初始化所以根据这个变量应该在编译时已知(将静态变量的初始值设置为编译时常量。-https://en.cppreference.com/w/cpp/language/constant_initialization)但这并不意味着常量,所以我们有编译时非常量变量,这个static_assert不能工作。
  5. Map 被初始化为 constinit const 变量。现在我们有了编译时常量变量,但 static_assert 拒绝工作。 static_assert needs contextually converted constant expression of type bool (https://en.cppreference.com/w/cpp/language/static_assert) which is A converted constant expression of type T 是隐式转换为类型 T 的表达式,其中转换后的表达式为常量表达式。 为什么这不起作用?

根据 cppInsights,编译器生成的代码与 constinit const 和 constexpr 相同。

Map is initialized as constinit const variable. Now we have compile-time constant variable but static_assert refuses to work

第二种说法与第一种说法不同。我们 没有 有一个 compile-time 常量变量,因此 static_assert 不起作用。

constinit 不会使您的变量成为 constexpr 变量,它仅保证您具有常量初始化(因此得名 constinit)。事实上,constinit 甚至不意味着 const:

constinit std::mutex m;

constinit 的有效和激励用途,并且仍然允许我锁定和解锁 m

拥有 constexpr 变量的唯一方法是声明您的变量 constexpr(不幸的是,对声明的整数类型 const 进行了遗留分割,这在此处不适用).如果你想拥有一个 constexpr 地图,你需要声明你的地图 constexpr.

Map is initialized as constinit const variable. ... but static_assert refuses to work. ... Why this does not work?

如您所见,static_assert 需要表达式在编译时已知。

然而,const仅表示逻辑常量,并不表示该值在编译时已知(忽略使用常量表达式初始化的 const 整数值的情况)。

类似地,constinit仅表示静态初始化;同样,这并不意味着该值在编译时已知。

从你问题的措辞来看,我猜你是在期待:

const + constinit --> constexpr

但事实并非如此。如果您希望 Map 在编译时可用(即在 static_assert 内,您需要将其设为 constexpr)。

您试图从错误的角度解决问题。您会看到一个声明为 constinit const 的变量。你认为,因为对象是 non-modifiable,并且因为它是由常量表达式初始化的,所以这意味着对象是常量表达式。

不是。

编译器可以知道它的值吗?绝对地。但这不是“常量表达式”的定义方式。

某物是常量表达式,因为标准规定它是。声明为 constinit 的变量不是常量表达式,因为规则没有说明它是。声明为 const 的变量不是常量表达式,因为规则并未说明它是常量表达式(除了某些整数情况,它早于 constexpr)。这两种标记的使用没有特殊规则。

如果变量被声明为 constexpr(或那些 const 整数异常之一),则该变量是常量表达式。并且static_assert.

中只能出现常量表达式

这是规则。

并且没有理由使用 constinit const 的特殊情况,因为如果你想要一个常量表达式......你可以只写 constexpr。毕竟,您可能在某些模板代码中有人给了您 T 而恰好是 const,而您在某处创建了一个 constinit T 变量。您没有要求它成为常量表达式;您只需要一个静态初始化的变量。类型恰好是 const 只是偶然。

当然,编译器可以利用这些知识自由地进行各种特殊优化。但这与允许编译器做什么无关;它是关于语言的意思。如果您想从该声明中获得特殊含义,您应该说对了。