我在具有std::visit()许多替代方案的变体上使用 C++17 的函数,每当我忘记访问者中的一个或多个替代方案时,编译器生成的错误消息都非常难以理解。
例如
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
using Foo = std::variant<A, B, /* ... many more alternatives ... */>;
Foo foo;
std::visit(overloaded{
[](A const& a) { /* ... */ },
[](B const& b) { /* ... */ },
/* ... forgot 1+ alternatives ... */
}, foo
);
Run Code Online (Sandbox Code Playgroud)
在上面的代码示例中,编译器可以生成长度为数千个字符的错误消息,具体取决于替代方案的数量。有没有办法改进这些错误消息,以便编译器输出类似以下内容?
example.cc:8-13: error: Non-exhaustive visitor -- missing alternative of type 'X'
Run Code Online (Sandbox Code Playgroud) 我有一个不可复制但可移动的资源包装类。像这样的东西(伪代码)
class Wrapper {
SomeResource* m_handle = nullptr;
public:
Wrapper(const Wrapper&) = delete;
Wrapper& operator=(const Wrapper&) = delete;
Wrapper(SomeResource* handle) : m_handle(handle) { }
Wrapper(Wrapper&& other) {
std::swap(m_handle, other.m_handle);
}
}
Run Code Online (Sandbox Code Playgroud)
这一切都很好,但是我有很多这些,并且我有一个函数可以解析一些数据并返回包装器或替代包装器。我想到用std::variant返回值来表达这一点。例如
std::variant<Wrapper, AlternativeWrapper> LoadData(void* bytes, size_t len)
{ ... }
Run Code Online (Sandbox Code Playgroud)
我可以编写这个函数,并且它全部可以编译。即,我在 LoadData 函数内构造一个 Wrapper,然后我可以将其移动到随后返回的变体中。
但另一方面,当我想获取值时,出现此错误(MSVC2019)
error C2280: 'Wrapper::Wrapper(const Wrapper&)': attempting to reference a deleted function
Run Code Online (Sandbox Code Playgroud)
我的代码看起来像这样。
auto result = LoadData(bytes, len);
std::get<Wrapper>(result);
Run Code Online (Sandbox Code Playgroud)
这是有道理的,因为结果仍然存在,但是我如何访问它呢?
For example, it should be very helpful to equal compare a std::variant<T1, T2> with a T1 or T2. So far we can only compare with the same variant type.
由于std::variant不允许在标准库中与其替代类型之一进行比较,我正在使用 C++20<=>运算符实现比较函数:
template <typename... Args, typename T>
constexpr auto operator<=>(const std::variant<Args...>& v, const T& t) {
return std::visit([&t](const auto& u) -> std::partial_ordering {
if constexpr (requires { u <=> t; }) return u <=> t;
else return std::partial_ordering::unordered;
}, v);
}
Run Code Online (Sandbox Code Playgroud)
但是当我使用自己定义的测试上述函数时std::variant:
using Variant = std::variant<double, int, std::string_view>;
constexpr Variant v1{1.0};
constexpr Variant v2{1};
constexpr Variant v3{"hello"};
static_assert(v1 < 2);
// compile error
static_assert(v2 < 2);
static_assert(!(v3 > 2) && !(v3 < 2) && …Run Code Online (Sandbox Code Playgroud) 考虑以下:
struct foo {
};
struct bar {
};
int main()
{
foo f;
bar b;
std::variant<foo*, bool> v;
v = &b; // compiles in Visual Studio 19 v16.7.3
}
Run Code Online (Sandbox Code Playgroud)
正如评论中所讨论的,我相信以上是合法的 C++17。有一个提案P0608R3已被标准接受,以解决这种令人惊讶的行为,但它在 2018 年(在圣地亚哥会议上)被接受,因此适用于 C++20 而不是 C++17。此外,P0608R3 当前未在 Visual Studio 中实现,即使在编译到 C++20 预览版时也是如此。
从指向非 foo 的指针创建此变体的最佳/最不冗长的方法是编译时错误?我相信以下内容有效,但如果变体包含多个项目,则是很多样板。
struct foo {
};
struct bar {
};
using variant_type = std::variant<foo*, bool>;
struct var_wrapper : public variant_type
{
var_wrapper(foo* v = nullptr) : variant_type(v)
{}
var_wrapper(bool v) : variant_type(v)
{}
template<typename …Run Code Online (Sandbox Code Playgroud) 这主要是琐碎的问题,因为我怀疑我是否需要节省空间。
在玩Godbolt 时,我注意到 libstdc++ 和 libc++ 的实现都std::variant需要超过 1 个字节来存储空结构的变体。
libstc++ 使用 2 个字节
libc++ 使用 8 个字节
我认为优化这个是不值得的,但我想知道是否还有其他原因。特别是在标准措辞中是否std::variant有阻止这种优化的东西。
如果我们有这样的代码:
#include <variant>
int main(){
using V = std::variant<int, double>;
V a = 5;
V b = 5.6;
a.swap(b);
}
Run Code Online (Sandbox Code Playgroud)
https://gcc.godbolt.org/z/oqGiHs
如果您使用 clang 进行编译,它会发出处理swap.
这是为什么?两种变体都是非空的,底层类型是异常安全的。
具有讽刺意味的是,这无一例外地编译:
#include <variant>
template<class T>
void sw(T &a, T &b){
auto c = a;
a = b;
b = c;
}
int main(){
using V = std::variant<int, double>;
V a = 5;
V b = 5.6;
sw(a, b);
}
Run Code Online (Sandbox Code Playgroud) 下面的代码
#include <optional>
#include <string>
#include <variant>
constexpr bool USE_VARIANT = 1;
using T = std::conditional_t<USE_VARIANT, std::variant<std::string>, std::string>;
struct S
{
T t;
};
constexpr int func()
{
S s{"str"};
return 0;
}
constexpr int x = func();
int main()
{
}
Run Code Online (Sandbox Code Playgroud)
无法使用 GCC trunk(编译器资源管理器链接)进行编译,并显示以下消息
In file included from <source>:4:
<source>:21:23: in 'constexpr' expansion of 'func()'
<source>:19:1: in 'constexpr' expansion of '(& s)->S::~S()'
<source>:10:8: in 'constexpr' expansion of '((S*)this)->S::t.std::variant<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::~variant()'
/opt/compiler-explorer/gcc-trunk-20211231/include/c++/12.0.0/variant:1408:28: in 'constexpr' expansion of '((std::variant<std::__cxx11::basic_string<char, …Run Code Online (Sandbox Code Playgroud) std::variant我已经知道如何很好地使用std::get_if(),std::get()和std::visit()。但是,如果我只是想要一种简单的方法来判断变体是否已初始化为任何值怎么办?也就是说,我不在乎值是什么,我只想要一个布尔测试。我怎么做?
例如,假设我在堆栈上声明一个变体:
std::variant<int, double> data;
Run Code Online (Sandbox Code Playgroud)
然后,我的函数继续运行,可能会也可能不会初始化该变量。在函数结束时,我想测试它是否已初始化。
index()函数。对于未初始化的变体和我初始化为声明的第一个类型的变体都返回 0 。valueless_by_exception()函数,但无论我是否初始化变体,它都会返回 false。我唯一能想到的是将它与默认构造的进行比较,如下所示:
using Data = std::variant<int, double>;
Data data;
// ... code here that might initialize data or might not...
if (data == Data())
// Not initialized
else
// Initialized
Run Code Online (Sandbox Code Playgroud)
这似乎有效,但阅读for的评论operator==()std::variant,似乎这种行为是未定义的。
那么,这是一种安全的测试方法,还是还有其他方法?
例如,我尝试使用 实现 AST std::variant,其中 Token 可以是数字、关键字或变量。其中数字由 表示int,关键字和变量由 表示std::string:
enum TokenType : std::size_t {
Number = 0, Keyword = 1, Variable = 2,
};
using Token = std::variant<int, std::string, std::string>;
Token token(std::in_place_index<TokenType::Variable>, "x");
Run Code Online (Sandbox Code Playgroud)
当我想用 做某事时token,我可以首先确定它的类型token.index(),然后决定要做什么:
switch (token.index()) {
case TokenType::Number:
return do_sth_with_number(std::get<TokenType::Number>(token));
case TokenType::Keyword:
return do_sth_with_keyword(std::get<TokenType::Keyword>(token));
case TokenType::Variable:
return do_sth_with_variable(std::get<TokenType::Variable>(token));
}
Run Code Online (Sandbox Code Playgroud)
不过,我不知道是否可以使用它std::visit来达到同样的效果。我只知道它可以根据变体中的数据类型调用特定函数,但我不知道是否可以根据索引来执行此操作。
我知道我可以通过将关键字和变量包装在两个不同的类中来实现我的目标,但我想知道是否有更好的方法来做到这一点,因为据我了解,在变体中,决定使用哪个函数应该更直接基于索引而不是类型使用。