C++ 变体访问重载函数

问题描述 投票:0回答:4

我想在变体上执行重载函数。以下代码块可以工作并编译,但

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);
}

有没有更简单的方法?

c++ variant
4个回答
10
投票
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++ 理解我们的意思,那就太好了,但这要么需要

std::visit
的特殊 C++ 语法,要么需要多方法形式的运行时调度。不管怎样,这不太可能很快发生,或者根本不会发生。


4
投票

有没有更简单的方法? 好吧,可能有一种语法上更精简的方式:

  1. 您可以在 std::visit 内部使用 cppreference 示例中的重载{}:
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 推导指南

  1. 或者更好的是,您可以使用相同的
    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);
}

我在

这个 repo
中有这个和更多的语法糖(例如 |is 和 |as “operator-lookalikes”,用于
variant
any :)


1
投票

@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(类模板参数推导)需要推导指南


0
投票

如果我想将参数作为 const ref 传递,如何定义重载?

即:

overload ovld {
[](const A &a) { std::cout << a.name << std::endl; },
[](const B &b) { std::cout << b.type << std::endl; },
[](const C &c) { std::cout << c.age << std::endl; }
};
© www.soinside.com 2019 - 2024. All rights reserved.