Home > Mobile >  Perfect forwarding in the visitor pattern std::visit of C
Perfect forwarding in the visitor pattern std::visit of C

Time:03-28

I was watching Jason Turner C weekly and I came across this code snippet.

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

I can get rid of the the perfect forwarding as well as the deduction guide. Is this not considered the best practice in C . In what situations is it less efficient?

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

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

e

CodePudding user response:

I can get rid of the perfect forwarding

Passing by a const reference would copy the lambdas instead of moving them.

It doesn't matter in your case, but it will matter if the lambdas capture things by value. It can hurt performance if the lambdas are expensive to copy, or cause a compilation error if the lambdas can't be copied (because they have non-copyable state, e.g. [x = std::make_unique<int>()](){}).

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

If you pass by const reference, yes. But if you pass by a forwarding reference, no.

The constructor having an independent set of template parameters is one problem. Buf if you used the same template parameters (and inherited from std::decay_t<B>... instead), it still wouldn't work, because built-in deduction guides refuse to do perfect forwarding. The template argument deduction wouldn't work if you passed an lvalue.


A better question is why have a constructor at all. If you remove it, the type becomes an aggregate and everything continues to work as before.

It even lets you remove the deduction guide (except that Clang doesn't support that yet).

CodePudding user response:

Visitor(const B&... b)

All your parameters to this constructor will now be const references...

                        : B(b)... 

... and if any of these subclasses' constructors take a corresponding parameter that's a non-const reference, this will be ill-formed and not compile.

class Foo {

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

Try using your visitor with this class, to see if it works. Your template instance's constructor should turn out to be something like

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