从 uint32_t 构造的 std::variant 使用 GCC 8.2.0 比 std::optional<uint32_t> 更喜欢持有 int32_t
std::variant constructed from uint32_t prefers to hold int32_t than std::optional<uint32_t> using GCC 8.2.0
我有以下代码:
#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;
}
代码非常简单明了,但我遇到了一个奇怪的行为。
当我使用 gcc 8.2 编译它时,return 代码为 0,输出控制台上没有 'missing' 消息,这表明变体是使用 int32_t.[=17= 构造的]
另一方面,当我使用 gcc 10.2 编译它时,它的行为相反。
我试图弄清楚标准中发生了什么变化可以解释这种行为。
这里还有编译器资源管理器link:click
这是一个简化版本:
constexpr int f() {
return std::variant<int32_t, std::optional<uint32_t>>(0U).index();
}
对于 gcc 8.3,f() == 0
但对于 gcc 10.2,f() == 1
。这里的推理最终是变体初始化是......复杂的。
最初,当 C++17 发布时,从表达式 E
初始化 variant<T, U>
的方式基本上是通过重载决议来确定索引。像这样:
constexpr int __index(T) { return 0; }
constexpr int __index(U) { return 1; }
constexpr int which_index == __index(E);
在此特定示例中,T=int32_t
和 U=optional<uint32_t>
,并且 E
是类型 uint32_t
的表达式。这个重载决议会给我们 0
:从 uint32_t
到 int32_t
的转换比从 uint32_t
到 [=] 的转换 更好 [=78=] 28=](前者为标准,后者为user-defined)。您可以验证这一点:
constexpr int __index(int32_t) { return 0; }
constexpr int __index(std::optional<uint32_t>) { return 1; }
static_assert(__index(0U) == 0);
但是这个规则有一些令人惊讶的结果。这在 P0608 中进行了总结,其中包括这个例子:
variant<string, bool> x = "abc"; // holds bool
这是因为到 bool
的转换仍然是标准转换,而到 string
的转换是 user-defined。这……很可能不是用户想要的。
所以新规则最终是(因为通过 P1957 进一步修改)在我们进行一轮重载决策以确定索引之前,我们首先将类型列表修剪为那些不是'缩小转换范围。也就是说,包中的那些类型 T<sub>i</sub>
:
Ti x[] = {E};
是一个有效的表达式。那就是 不再 对 bool x[] = {"abc"};
有效,这就是 variant<string, bool>
示例现在根据需要持有 string
的原因。
但对于此处的原始示例,int32_t x[] = {u};
(u
是一个 uint32_t
)不是有效的声明 - 这是一个缩小转换(这适用于 0U
直接,但我们在进行此检查时丢失了 constant-ness)。
一旦应用了这个缺陷报告,我们现在就有了这个重载集:
// constexpr int __index(int32_t) { return 0; } // removed from consideration
constexpr int __index(std::optional<uint32_t>) { return 1; }
static_assert(__index(0U) == 1);
这就是为什么您的 variant
现在持有 optional<uint32_t>
而不是 int32_t
。
The code is pretty simple and straightforward
我希望你现在认识到尝试从既不是 T
也不是 U
的类型初始化 variant<T, U>
并不简单或直接。
我有以下代码:
#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;
}
代码非常简单明了,但我遇到了一个奇怪的行为。 当我使用 gcc 8.2 编译它时,return 代码为 0,输出控制台上没有 'missing' 消息,这表明变体是使用 int32_t.[=17= 构造的]
另一方面,当我使用 gcc 10.2 编译它时,它的行为相反。 我试图弄清楚标准中发生了什么变化可以解释这种行为。
这里还有编译器资源管理器link:click
这是一个简化版本:
constexpr int f() {
return std::variant<int32_t, std::optional<uint32_t>>(0U).index();
}
对于 gcc 8.3,f() == 0
但对于 gcc 10.2,f() == 1
。这里的推理最终是变体初始化是......复杂的。
最初,当 C++17 发布时,从表达式 E
初始化 variant<T, U>
的方式基本上是通过重载决议来确定索引。像这样:
constexpr int __index(T) { return 0; }
constexpr int __index(U) { return 1; }
constexpr int which_index == __index(E);
在此特定示例中,T=int32_t
和 U=optional<uint32_t>
,并且 E
是类型 uint32_t
的表达式。这个重载决议会给我们 0
:从 uint32_t
到 int32_t
的转换比从 uint32_t
到 [=] 的转换 更好 [=78=] 28=](前者为标准,后者为user-defined)。您可以验证这一点:
constexpr int __index(int32_t) { return 0; }
constexpr int __index(std::optional<uint32_t>) { return 1; }
static_assert(__index(0U) == 0);
但是这个规则有一些令人惊讶的结果。这在 P0608 中进行了总结,其中包括这个例子:
variant<string, bool> x = "abc"; // holds bool
这是因为到 bool
的转换仍然是标准转换,而到 string
的转换是 user-defined。这……很可能不是用户想要的。
所以新规则最终是(因为通过 P1957 进一步修改)在我们进行一轮重载决策以确定索引之前,我们首先将类型列表修剪为那些不是'缩小转换范围。也就是说,包中的那些类型 T<sub>i</sub>
:
Ti x[] = {E};
是一个有效的表达式。那就是 不再 对 bool x[] = {"abc"};
有效,这就是 variant<string, bool>
示例现在根据需要持有 string
的原因。
但对于此处的原始示例,int32_t x[] = {u};
(u
是一个 uint32_t
)不是有效的声明 - 这是一个缩小转换(这适用于 0U
直接,但我们在进行此检查时丢失了 constant-ness)。
一旦应用了这个缺陷报告,我们现在就有了这个重载集:
// constexpr int __index(int32_t) { return 0; } // removed from consideration
constexpr int __index(std::optional<uint32_t>) { return 1; }
static_assert(__index(0U) == 1);
这就是为什么您的 variant
现在持有 optional<uint32_t>
而不是 int32_t
。
The code is pretty simple and straightforward
我希望你现在认识到尝试从既不是 T
也不是 U
的类型初始化 variant<T, U>
并不简单或直接。