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 初始化映射。我把每一种可能性都注释掉了,我想解释一下。
- Map 被初始化为 非常量 变量。我知道这是行不通的,它不是常量变量,也不是编译时初始化的
- Map 被初始化为 const 变量。这也不行,即使变量是常量,也不能保证在编译时创建。
- Map 被初始化为 constexpr 变量。这保证变量将在编译时初始化。它还意味着常量,所以我们有编译时常量变量。这工作正常。 (https://en.cppreference.com/w/cpp/language/constexpr)
- 地图被初始化为constinit变量。现在,constinit 保证表达式是零初始化的或常量初始化的。我用常量初始化所以根据这个变量应该在编译时已知(将静态变量的初始值设置为编译时常量。-https://en.cppreference.com/w/cpp/language/constant_initialization)但这并不意味着常量,所以我们有编译时非常量变量,这个static_assert不能工作。
- 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
只是偶然。
当然,编译器可以利用这些知识自由地进行各种特殊优化。但这与允许编译器做什么无关;它是关于语言的意思。如果您想从该声明中获得特殊含义,您应该说对了。
我有一个关于编译时函数的问题。我知道 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 初始化映射。我把每一种可能性都注释掉了,我想解释一下。
- Map 被初始化为 非常量 变量。我知道这是行不通的,它不是常量变量,也不是编译时初始化的
- Map 被初始化为 const 变量。这也不行,即使变量是常量,也不能保证在编译时创建。
- Map 被初始化为 constexpr 变量。这保证变量将在编译时初始化。它还意味着常量,所以我们有编译时常量变量。这工作正常。 (https://en.cppreference.com/w/cpp/language/constexpr)
- 地图被初始化为constinit变量。现在,constinit 保证表达式是零初始化的或常量初始化的。我用常量初始化所以根据这个变量应该在编译时已知(将静态变量的初始值设置为编译时常量。-https://en.cppreference.com/w/cpp/language/constant_initialization)但这并不意味着常量,所以我们有编译时非常量变量,这个static_assert不能工作。
- 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 butstatic_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
只是偶然。
当然,编译器可以利用这些知识自由地进行各种特殊优化。但这与允许编译器做什么无关;它是关于语言的意思。如果您想从该声明中获得特殊含义,您应该说对了。