我有一个关于编译时函数的问题。我知道static_assert应该只处理可以在编译时评估/计算的类型。所以它不适用于 std::string(尚不支持 gcc10 对 constexpr std::string),但可以与 std::array(当我在编译时知道大小时)。我正在看杰森·特纳(Jason Turner)的《C++周刊》,所以这个片段来自 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 初始化地图。我注释掉了所有可能性,我想解释一下。
- 映射初始化为非常量变量。我知道这是行不通的,它不是常量变量,也不是编译时初始化
- 映射初始化为常量变量。这也行不通,即使变量是常量,也不能保证它会在编译时创建。
- 映射初始化为constexpr变量。这保证了变量将在编译时初始化。它也意味着恒定性,所以我们有编译时常量变量。这工作正常。(https://en.cppreference.com/w/cpp/language/constexpr)
- 映射初始化为连续变量。现在,constinit 保证表达式是零初始化或常量初始化的。我用常量初始化,所以根据这个变量应该在编译时知道(将静态变量的初始值设置为编译时常量。 - https://en.cppreference.com/w/cpp/language/constant_initialization) 但它并不意味着恒定性,所以我们有编译时非常量变量,这个static_assert不起作用。
- 映射被初始化为constinit const变量。现在我们有编译时常量变量,但static_assert拒绝工作。static_assert需要上下文转换的 bool (https://en.cppreference.com/w/cpp/language/static_assert) 类型的常量表达式,即 T 类型的转换常量表达式是隐式转换为类型 T 的表达式,其中转换后的表达式是常量表达式。为什么这不起作用?
根据cppInsights,编译器生成的代码与constinit const和constexpr相同。
Map 初始化为
constinit const
变量。现在我们有编译时常量变量,但static_assert
拒绝工作
第二项索赔不是从第一项索赔开始的。我们没有编译时常量变量,因此static_assert
不起作用。
constinit
不会使您的变量成为constexpr
变量,它只能保证您具有常量初始化(因此得名constinit
)。事实上,constinit
甚至并不意味着const
:
constinit std::mutex m;
是constinit
的有效和激励用途,并且仍然允许我锁定和解锁m
。
拥有constexpr
变量的唯一方法是将变量声明为constexpr
(为声明const
的整数类型划出一个不幸的遗留问题,这在这里不适用)。如果要拥有constexpr
地图,则需要声明您的地图constexpr
。
你试图从错误的角度解决问题。您会看到一个声明为constinit const
的变量。您认为,由于对象是不可修改的,并且由于它由常量表达式初始化,这意味着该对象是常量表达式。
不是。
编译器是否可知其值?绝对。但这不是"常量表达"的定义方式。
某物是一个恒定的表达式,因为标准说它是。声明constinit
变量不是常量表达式,因为规则没有说它是常量表达式。声明const
的变量不是常量表达式,因为规则没有说它是常量表达式(除了某些早于constexpr
的整数情况)。而且这两个标记的使用没有特殊的规则。
如果变量声明为constexpr
(或整数异常之一const
变量),则该变量是常量表达式。并且只有常量表达式才能出现在static_assert
中。
这是规则。
而且没有理由使用constinit const
的特殊情况,因为如果您想要一个常量表达式......你可以写constexpr
.毕竟,您可能在某些模板代码中,有人给了您一个恰好const
T
,并且您在某处创建了一个constinit T
变量。你没有要求它是一个恒定的表达;您只需要一个静态初始化的变量。这种类型恰好是const
只是偶然的。
现在可以肯定的是,编译器可以自由地利用这些知识进行各种特殊优化。但这不是关于编译器被允许做什么;这是关于语言的含义。如果你想从这个宣言中得到特殊的含义,你应该正确地说出来。
Map 被初始化为 constinit const 变量。但static_assert拒绝工作。...为什么这不起作用?
正如您所观察到的,static_assert
需要在编译时知道表达式。
但是,const
仅暗示逻辑恒定性,并不意味着该值在编译时是已知的(忽略使用常量表达式初始化的常量整数值的情况)。
同样,constinit
仅意味着静态初始化;同样,这并不意味着该值在编译时是已知的。
从你问题的措辞来看,我想你期待的是:
const
+constinit
-->constexpr
但事实并非如此。如果您希望 Map 在编译时可用(即在static_assert
内部,则需要将其设为constexpr
)。