C++的访问者模式std::visit中的完美转发

Perfect forwarding in the visitor pattern std::visit of C++

我每周都在看 Jason Turner C++,我偶然发现了这个代码片段。

template<typename... B>
struct Visitor: B...{

template<typename... T>
Visitor(T&&...t): B(std::forward<T>(t))...{} 

using B::operator()...;
};

template<typename...T>
Visitor(T...) -> Visitor<std::decay_t<T>...>;

int main(){
    std::array<std::variant<double, int, std::string>, 6> a{3.2, 2, 4, 6, 2.4, "foo"};
    int intTotal = 0;
    double doubleTotal = 0.0;

    Visitor visitor {[&intTotal](const int i){intTotal += i;},
                  [&doubleTotal](const double d) { doubleTotal += d;},
                 [](const std::string& c) { }};

    std::for_each(begin(a), end(a), [&visitor](const auto &v) {std::visit(visitor, v);});
    std::cout << intTotal << " " << doubleTotal << std::endl;
}

我可以去掉完美转发和推导。这不被认为是 C++ 中的最佳实践吗?什么情况下效率低?

template<typename... B>
struct Visitor: B...{

  Visitor(const B&... b): B(b)... { }
  using B::operator()...;
};

e

Visitor(const B&... b)

此构造函数的所有参数现在都将是 const 引用...

                        : B(b)... 

... 如果这些子classes 的构造函数中的任何一个采用非const 引用的相应参数,这将是ill-formed 而不是编译。

class Foo {

public:
    Foo(int &n) {}
};

尝试将您的访问者与此 class 一起使用,看看它是否有效。您的模板实例的构造函数应该类似于

Visitor(const int &n) : Foo(n) {}

I can get rid of the perfect forwarding

通过 const 引用传递将复制 lambda,而不是移动它们。

在您的情况下这无关紧要,但如果 lambda 表达式按值捕获事物就很重要了。如果 lambda 的复制成本很高,可能会损害性能,或者如果无法复制 lambda 会导致编译错误(因为它们具有 non-copyable 状态,例如 [x = std::make_unique<int>()](){})。

I can get rid of the ... the deduction guide.

如果你通过 const 引用,是的。但是如果你通过转发参考,没有。

具有一组独立模板参数的构造函数是一个问题。但是如果你使用相同的模板参数(而是从 std::decay_t<B>... 继承),它仍然不会工作,因为 built-in 推导指南拒绝做完美转发。如果传递左值,模板参数推导将不起作用。


更好的问题是为什么要有构造函数。如果您删除它,该类型将变成一个聚合,并且一切都将像以前一样继续工作。

它甚至可以让您删除演绎指南(除了 Clang 尚不支持)。