C++ Logo

std-proposals

Advanced search

Re: std::variant - going from Alternative& to the enclosing variant&

From: Victor Khomenko <victor.khomenko_at_[hidden]>
Date: Tue, 8 Sep 2020 15:29:58 +0000
> * If you wish to re-use the visitor, you need a mechanism to update
> the captured reference, and you must remember to update it every time you
> call the visitor, and there can be many call sites.
>
> In the code I showed, the visitor is stateless; it has no captures.

In your example, the visitor is the lambda that captures variables "foo" and "visitor" by reference, not the variable "visitor", as you call std::visit on the lambda. I guess your point is that this lambda is cheap to create, so one can do it at every call. Now, it would be ugly to write this lambda if there are many call sites, so one should instead wrap your code in a special visitation function - which is the 3rd workaround in my original posting:

    MyVariant foo;
    Visitor visitor;
    special_visit(visitor, foo); // this function is a wrapper for the code you suggested

This works ok in many cases, but is dangerous with rvalue references, see below. It also requires all operator()s have an extra parameter, even if only a few of them use it.


> * If the variant is an rvalue, you end up holding two references (of
> different types) to the same object, so you either forfeit rvalue-ness or break
> the unique ownership semantics.
>
> Can you show an example?

My AST examples are too long to be posted, so I'd rather make up an artificial one. There is a question of which of the workarounds one should use - let's use the one with special_visit function. The motivation for && is that one can safely re-use subtrees (their root nodes may be re-created).

  using ASTNode = variant<Constant, Variable, UnaryMinus, BinaryMinus, + 100 more>;

  // this visitor takes ASTNode&& and returns a vector of "useful" sub-trees
  struct CollectUsefulSubtreesVisitor{
     unordered_map<Blah> context; // large, so copying is expensive
     vector<ASTNode> useful; // accumulates "useful" subtrees

     // simplifies recursive calls
     void recurse(ASTNode&& n){
  special_visit(*this,move(n));
     }

     void operator()(Constant&&, ASTNode&&){ // 2nd parameter is not used but passed anyway :-(
  // constants are never "useful"
     }

     void operator()(Variable&& v, ASTNode&& n){
  // breaks unique ownership, even if one of the &&s is replaced by &
  useful.push_back(move(n)); // variables are always "useful"; make use of 2nd parameter
  // fiddle with the context
  fiddle(context,v); // Ouch!!! moving n tacitly broke v!
     }

     void operator()(UnaryMinus&& um, ASTNode&&){ // 2nd parameter is not used but passed anyway :-(
  recurse(move(um.get_operand()));
     }

     void operator()(BinaryMinus&& bm, ASTNode&& n){
  n = BinaryPlus(move(bm.lhs), ASTNode(UnaryMinus(move(bm.rhs)))); // A - B = A + (-B)
  recurse(move(n));
     }

     void operator()(BinaryPlus&& b, ASTNode&&){ // 2nd parameter is not used but passed anyway :-(
  // operands of + are "useful"
  useful.push_back(move(bm.lhs));
  useful.push_back(move(bm.rhs));
     }

      ... // and many other operator()s
  };

  ASTNode tree(create_tree());
  CollectUsefulSubtreesVisitor v;
  special_visit(v,tree);
  // do something with v.useful

Received on 2020-09-08 10:33:30