C++ Variant访问重载函数
C++ Variant visit overloaded function
我想在变体上执行重载函数。以下代码块可以运行并编译,但 visit
调用似乎过于复杂。为什么我不能简单地写:
std::visit(&f, something);
工作版本和上下文:
#include <variant>
#include <string>
#include <iostream>
#include <functional>
struct A {
std::string name = "spencer";
};
struct B {
std::string type = "person";
};
struct C {
double age = 5;
};
void f(A a) {
std::cout << a.name << std::endl;
}
void f(B b) {
std::cout << b.type << std::endl;
}
void f(C c) {
std::cout << c.age << std::endl;
}
int main() {
std::variant<A, B, C> something{B{}};
std::visit([](auto&& x) {f(x);}, something);
}
有没有更简单的方法?
std::visit(&f, something);
这是无效的,因为 f
不是一个函数。 &f
说“给我指向 f
的指针”。但是 f
不是一回事;这三个函数恰好共享一个名称,具有三个独立的指针。
std::visit([](auto&& x) {f(x);}, something);
这将创建一个基于模板的闭包,该模板生成代码以在编译时执行分派。实际上,它就像我们做的那样工作
void f(A a) {
std::cout << a.name << std::endl;
}
void f(B b) {
std::cout << b.type << std::endl;
}
void f(C c) {
std::cout << c.age << std::endl;
}
struct F {
template<typename T>
void operator()(T x) {
f(x);
}
};
int main() {
std::variant<A, B, C> something{B{}};
std::visit(F(), something);
}
这会强制 C++ 编译器在模板扩展期间生成类似
的内容
void f(A a) {
std::cout << a.name << std::endl;
}
void f(B b) {
std::cout << b.type << std::endl;
}
void f(C c) {
std::cout << c.age << std::endl;
}
struct F {
void operator()(A x) {
f(x);
}
void operator()(B x) {
f(x);
}
void operator()(C x) {
f(x);
}
};
int main() {
std::variant<A, B, C> something{B{}};
std::visit(F(), something);
}
如果你想消除 lambda 包装器,你需要一个可以作为参数传递的可调用对象,而函数指针是不够的,因为函数指针不能进行重载解析。我们总是可以显式地创建一个仿函数对象。
struct F {
void operator()(A a) {
std::cout << a.name << std::endl;
}
void operator()(B b) {
std::cout << b.type << std::endl;
}
void operator()(C c) {
std::cout << c.age << std::endl;
}
};
int main() {
std::variant<A, B, C> something{B{}};
std::visit(F(), something);
}
是否认为这种方法比以前的方法更清洁取决于您。一方面,它更像是传统的 OOP 访问者模式,因为我们有一个对象进行访问。另一方面,如果我们可以传递函数的 name 并让 C++ 理解我们的意思,那就太好了,但这将需要特殊的 C++ 语法 std::visit
或多方法形式的运行时调度。无论哪种方式,它都不太可能很快发生,或者根本不会发生。
@Silvio 答案的一个变体是创建一个模板 overload
继承自您的函数的类型:
#include <variant>
#include <iostream>
struct A {
std::string name = "spencer";
};
struct B {
std::string type = "person";
};
struct C {
double age = 5;
};
template<typename...Func>
struct overload : Func... {
using Func::operator()...;
};
template<typename...Func> overload(Func...) -> overload<Func...>;
int main()
{
overload ovld {
[](A a) { std::cout << a.name << std::endl; },
[](B b) { std::cout << b.type << std::endl; },
[](C c) { std::cout << c.age << std::endl; }
};
std::variant<A, B, C> something{B{}};
std::visit(ovld, something);
}
C++17 之前,聚合 CTAD 需要推导指南(Class 模板参数推导)
Is there a simpler way?
Well, there might be a syntactically more streamlined way:
- 您可以在 std::visit 内的 cppreference example 使用重载{}:
std::visit(overloaded{
[](A a) { std::cout << a.name << std::endl; },
[](B b) { std::cout << b.type << std::endl; },
[](C c) { std::cout << c.age << std::endl; }
}, something);
在这里,overloaded
实现非常简单,唯一感兴趣的可能是推导指南,以防您还不熟悉 C++17 推导指南
- 或者更好的是,您可以对相同的
overloaded
结构做一些改动,使之成为可能:
something| match {
[](A a) { std::cout << a.name << std::endl; },
[](B b) { std::cout << b.type << std::endl; },
[](C c) { std::cout << c.age << std::endl; }
};
我个人比较喜欢后一个版本,因为它更加精简,而且它类似于其他一些语言中的 match
从句。
实施实际上非常简单,仅有的 2 处变化是:
- 你把
overloaded
重命名为你喜欢的名字,这里是match
- 你重载了运算符|让它工作
template <typename... Ts, typename... Fs>
constexpr decltype(auto) operator| (std::variant<Ts...> const& v, match<Fs...> const& match) {
return std::visit(match, v);
}
我在 this repo 中有这个和更多语法糖(例如 variant
和 any
的 |is 和 |as "operator-lookalikes"):)
我想在变体上执行重载函数。以下代码块可以运行并编译,但 visit
调用似乎过于复杂。为什么我不能简单地写:
std::visit(&f, something);
工作版本和上下文:
#include <variant>
#include <string>
#include <iostream>
#include <functional>
struct A {
std::string name = "spencer";
};
struct B {
std::string type = "person";
};
struct C {
double age = 5;
};
void f(A a) {
std::cout << a.name << std::endl;
}
void f(B b) {
std::cout << b.type << std::endl;
}
void f(C c) {
std::cout << c.age << std::endl;
}
int main() {
std::variant<A, B, C> something{B{}};
std::visit([](auto&& x) {f(x);}, something);
}
有没有更简单的方法?
std::visit(&f, something);
这是无效的,因为 f
不是一个函数。 &f
说“给我指向 f
的指针”。但是 f
不是一回事;这三个函数恰好共享一个名称,具有三个独立的指针。
std::visit([](auto&& x) {f(x);}, something);
这将创建一个基于模板的闭包,该模板生成代码以在编译时执行分派。实际上,它就像我们做的那样工作
void f(A a) {
std::cout << a.name << std::endl;
}
void f(B b) {
std::cout << b.type << std::endl;
}
void f(C c) {
std::cout << c.age << std::endl;
}
struct F {
template<typename T>
void operator()(T x) {
f(x);
}
};
int main() {
std::variant<A, B, C> something{B{}};
std::visit(F(), something);
}
这会强制 C++ 编译器在模板扩展期间生成类似
的内容void f(A a) {
std::cout << a.name << std::endl;
}
void f(B b) {
std::cout << b.type << std::endl;
}
void f(C c) {
std::cout << c.age << std::endl;
}
struct F {
void operator()(A x) {
f(x);
}
void operator()(B x) {
f(x);
}
void operator()(C x) {
f(x);
}
};
int main() {
std::variant<A, B, C> something{B{}};
std::visit(F(), something);
}
如果你想消除 lambda 包装器,你需要一个可以作为参数传递的可调用对象,而函数指针是不够的,因为函数指针不能进行重载解析。我们总是可以显式地创建一个仿函数对象。
struct F {
void operator()(A a) {
std::cout << a.name << std::endl;
}
void operator()(B b) {
std::cout << b.type << std::endl;
}
void operator()(C c) {
std::cout << c.age << std::endl;
}
};
int main() {
std::variant<A, B, C> something{B{}};
std::visit(F(), something);
}
是否认为这种方法比以前的方法更清洁取决于您。一方面,它更像是传统的 OOP 访问者模式,因为我们有一个对象进行访问。另一方面,如果我们可以传递函数的 name 并让 C++ 理解我们的意思,那就太好了,但这将需要特殊的 C++ 语法 std::visit
或多方法形式的运行时调度。无论哪种方式,它都不太可能很快发生,或者根本不会发生。
@Silvio 答案的一个变体是创建一个模板 overload
继承自您的函数的类型:
#include <variant>
#include <iostream>
struct A {
std::string name = "spencer";
};
struct B {
std::string type = "person";
};
struct C {
double age = 5;
};
template<typename...Func>
struct overload : Func... {
using Func::operator()...;
};
template<typename...Func> overload(Func...) -> overload<Func...>;
int main()
{
overload ovld {
[](A a) { std::cout << a.name << std::endl; },
[](B b) { std::cout << b.type << std::endl; },
[](C c) { std::cout << c.age << std::endl; }
};
std::variant<A, B, C> something{B{}};
std::visit(ovld, something);
}
C++17 之前,聚合 CTAD 需要推导指南(Class 模板参数推导)
Is there a simpler way? Well, there might be a syntactically more streamlined way:
- 您可以在 std::visit 内的 cppreference example 使用重载{}:
std::visit(overloaded{
[](A a) { std::cout << a.name << std::endl; },
[](B b) { std::cout << b.type << std::endl; },
[](C c) { std::cout << c.age << std::endl; }
}, something);
在这里,overloaded
实现非常简单,唯一感兴趣的可能是推导指南,以防您还不熟悉 C++17 推导指南
- 或者更好的是,您可以对相同的
overloaded
结构做一些改动,使之成为可能:
something| match {
[](A a) { std::cout << a.name << std::endl; },
[](B b) { std::cout << b.type << std::endl; },
[](C c) { std::cout << c.age << std::endl; }
};
我个人比较喜欢后一个版本,因为它更加精简,而且它类似于其他一些语言中的 match
从句。
实施实际上非常简单,仅有的 2 处变化是:
- 你把
overloaded
重命名为你喜欢的名字,这里是match
- 你重载了运算符|让它工作
template <typename... Ts, typename... Fs>
constexpr decltype(auto) operator| (std::variant<Ts...> const& v, match<Fs...> const& match) {
return std::visit(match, v);
}
我在 this repo 中有这个和更多语法糖(例如 variant
和 any
的 |is 和 |as "operator-lookalikes"):)