0

我有一个关于编译时函数的问题。我知道 static_assert 应该只适用于可以在编译时评估/计算的类型。所以它不适用于 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. Map 被初始化为constinit变量。现在, constinit 保证表达式是零初始化或常量初始化的。我用常量初始化,因此在编译时应该知道这个变量(将静态变量的初始值设置为编译时常量。- https://en.cppreference.com/w/cpp/language/constant_initialization)但是它并不意味着常量,所以我们有编译时非常量变量,这个 static_assert 不能工作。
  5. Map 被初始化为constinit con t 变量。现在我们有了编译时常量变量,但 static_assert 拒绝工作。static_assert 需要 bool 类型的上下文转换的常量表达式(https://en.cppreference.com/w/cpp/language/static_assert),它是 T 类型的转换常量表达式是隐式转换为 T 类型的表达式,其中转换后的表达式是一个常数表达式。为什么这不起作用?

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

4

3 回答 3

7

地图被初始化为constinit const变量。现在我们有编译时常量变量但static_assert拒绝工作

第二个主张不是从第一个主张而来的。我们没有编译时常量变量,因此static_assert不起作用。

constinit不会使您的变量成为constexpr变量,它仅保证您具有常量初始化(因此名称为constinit)。确实,constinit甚至不暗示const

constinit std::mutex m;

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

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

于 2020-10-01T14:37:08.963 回答
3

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

它不是。

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

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

如果声明了变量constexpr(或这些const整数异常之一),则变量是常量表达式。并且只有常量表达式可以出现在static_assert.

这就是规则。

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

现在可以肯定的是,编译器可以利用这些知识自由地进行各种特殊优化。但这与允许编译器做什么无关。这是关于语言的意思。如果你想从那个声明中得到特殊的含义,你应该说对了。

于 2020-10-01T15:11:34.220 回答
3

Map 被初始化为 constinit const 变量。...但 static_assert 拒绝工作。...为什么这不起作用?

正如您所观察到的,static_assert需要在编译时知道表达式。

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

同样,constinit只暗示静态初始化;同样,这并不意味着该值在编译时是已知的。

从您的问题的措辞来看,我猜您期望的是:

const+ constinit-->constexpr

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

于 2020-10-01T14:37:41.967 回答