我有以下代码:
#include <variant>
#include <optional>
#include <cstdint>
#include <iostream>
#include <type_traits>
using DataType_t = std::variant<
int32_t,
std::optional<uint32_t>
>;
constexpr uint32_t DUMMY_DATA = 0;
struct Event
{
explicit Event(DataType_t data)
: data_(data)
{}
template <class DataType>
std::optional<DataType> getData() const
{
if (auto ptr_data = std::get_if<DataType>(&data_))
{
return *ptr_data;
}
return std::nullopt;
}
DataType_t data_;
};
int main() {
auto event = Event(DUMMY_DATA);
auto eventData = event.getData<int32_t>();
if(!eventData) {
std::cout << "missing\n";
return 1;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
代码非常简单明了,但我遇到了一个奇怪的行为。当我使用 gcc 8.2 编译它时,返回代码为 0,并且输出控制台上没有“丢失”消息,这表明该变体是使用 int32_t 构造的。
另一方面,当我使用 gcc 10.2 编译它时,它的行为相反。我试图找出标准中发生了什么变化来解释这种行为。
这也是编译器资源管理器链接:单击
Bar*_*rry 15
这是一个简化版本:
constexpr int f() {
return std::variant<int32_t, std::optional<uint32_t>>(0U).index();
}
Run Code Online (Sandbox Code Playgroud)
对于 gcc 8.3,f() == 0但对于 gcc 10.2 f() == 1,. 这里的推理最终是变体初始化是......复杂的。
variant<T, U>最初,当 C++17 发布时,从表达式初始化 a 的方式E基本上是通过重载解析来确定索引。像这样的东西:
constexpr int __index(T) { return 0; }
constexpr int __index(U) { return 1; }
constexpr int which_index == __index(E);
Run Code Online (Sandbox Code Playgroud)
在此特定示例中,T=int32_tand U=optional<uint32_t>、 andE是类型 的表达式uint32_t。这种重载决策将给我们带来:从到 的0转换比从到 的转换更好(前者是标准的,后者是用户定义的)。您可以验证这一点:uint32_tint32_tuint32_toptional<uint32_t>
constexpr int __index(int32_t) { return 0; }
constexpr int __index(std::optional<uint32_t>) { return 1; }
static_assert(__index(0U) == 0);
Run Code Online (Sandbox Code Playgroud)
但这条规则有一些令人惊讶的结果。P0608对此进行了总结,其中包括以下示例:
variant<string, bool> x = "abc"; // holds bool
Run Code Online (Sandbox Code Playgroud)
这是因为转换为bool仍然是标准转换,而转换为string用户定义的。这……不太可能是用户想要的。
因此,新规则最终是(自P1957进一步修改以来),在我们进行一轮重载决策以确定索引之前,我们首先将类型列表修剪为不缩小转换的类型。也就是说,包中的那些类型:Ti
Ti x[] = {E};
Run Code Online (Sandbox Code Playgroud)
是一个有效的表达式。这对于不再有效bool x[] = {"abc"};,这就是为什么该variant<string, bool>示例现在string根据需要保留 a 的原因。
但对于这里的原始示例,int32_t x[] = {u};(u作为uint32_t) 不是一个有效的声明 - 这是一个缩小转换(这可以0U直接工作,但是当我们进行此检查时,我们失去了恒定性)。
应用此缺陷报告后,我们现在就设置了此过载:
// constexpr int __index(int32_t) { return 0; } // removed from consideration
constexpr int __index(std::optional<uint32_t>) { return 1; }
static_assert(__index(0U) == 1);
Run Code Online (Sandbox Code Playgroud)
这就是为什么你variant现在持有一个optional<uint32_t>而不是一个int32_t。
代码非常简单明了
我希望现在您已经认识到尝试从既不是也不是完全简单或直接的variant<T, U>类型初始化 a 。TU