8

I have code that resembles below.

typedef uint32_t IntType;
typedef IntType IntValue;
typedef boost::variant<IntValue, std::string>  MsgValue;

MsgValue v;

Instead of saying this,

IntValue value = boost::apply_visitor(d_string_int_visitor(), v);

I would like to pass an extra parameter like this: But operator() gives a compile error.

//This gives an error since the overload below doesn't work.
IntValue value = boost::apply_visitor(d_string_int_visitor(), v, anotherStr);

class d_string_int_visitor : public boost::static_visitor<IntType>
{
public:
    inline IntType operator()(IntType i) const
    {
        return i;
    }

    inline IntValue operator()(const std::string& str) const noexcept
    {
        // code in here
    }

    //I want this, but compiler error.
    inline IntValue operator()(const std::string& str, const std::string s) const noexcept
    {
        // code in here
    }
};
Praetorian
  • 106,671
  • 19
  • 240
  • 328
Ivan
  • 7,448
  • 14
  • 69
  • 134
  • What you wish to do with `anotherStr` ? – P0W Apr 14 '15 at 03:43
  • 1
    Why dont you pass `anotherStr` to the constructor of `d_string_int_visitor` and then use it? – Nawaz Apr 14 '15 at 04:19
  • Solutions by Preatorian and Yakk are not generic; they work only because the type of the extra parameter is one of the bounded types. This is so called binary (multi) visitation. If the extra type was not a bounded type, the only solution would be to use the constructor and member variable, as pointed out by Nawaz. – wmamrak May 01 '16 at 13:19

2 Answers2

11

You can bind the extra string argument to the visitor using std::bind. First, add the std::string parameter to all of the visitor's operator() overloads.

class d_string_int_visitor : public boost::static_visitor<IntType>
{
public:
    inline IntType operator()(IntType i, const std::string& s) const
    {
        return i;
    }

    inline IntValue operator()(const std::string& str, const std::string& s) const noexcept
    {
        // code in here
        return 0;
    }
};

Now create a visitor to which you have bound the second string argument.

auto bound_visitor = std::bind(d_string_int_visitor(), std::placeholders::_1, "Hello World!");
boost::apply_visitor(bound_visitor, v);

Live demo

However, a better solution would be to pass the string as the visitor's constructor argument.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Thanks Nawaz, Praetorian, I had a blind spot for some reason to passing it to d_string_int_visitor. But I am actually glad since this solution actually expanded my understanding of boost bind. – Ivan Apr 14 '15 at 04:40
  • Why would you use `std::bind` in C++14 for this? – Yakk - Adam Nevraumont Apr 14 '15 at 13:10
  • @Yakk Habit I guess (I still write C++03 at work). But, IMHO, this is one of those rare occasions where the lambda solution doesn't seem a lot more palatable compared to the `bind` solution. – Praetorian Apr 14 '15 at 15:19
  • @Praetorian my issue is that, from my perspective, `bind` is an obscure quirky function from a previous version of C++ used for niche issues; meanwhile lambdas are pretty central in modern C++. I'd expect a C++11 developer to be using lambdas every week, if not every day, in various contexts (not just this one). And `bind` is really quirky, esp if you start recursing (!). – Yakk - Adam Nevraumont Apr 14 '15 at 15:26
4
typedef uint32_t IntType;
typedef IntType IntValue;
typedef boost::variant<IntValue, std::string>  MsgValue;

MsgValue v;

IntValue value = boost::apply_visitor([&](auto&& one){
  return d_string_int_visitor{}(decltype(one)(one), anotherStr);
}, v);

assuming every overload of d_string_int_visitor can handle the extra parameter.

As a bonus, you can even do away with the wrapping class if you want:

IntValue to_int_value(IntValue v, std::string const& format) { return v; }
IntValue to_int_value(std::string const& str, std::string const& format);

IntValue value = boost::apply_visitor([&](auto&& one){
  return to_int_value(decltype(one)(one), anotherStr);
}, v);

where we create an anonymous lambda that forwards to a traditional set of function overloads.

The auto&& one and decltype(one)(one) is a technique to do perfect forwarding from a lambda (C++14). You could replace the second with std::forward<decltype(one)>(one), but I find the short version readable. Unlike std::forward, it does the "wrong" thing with value-types, but we know that one is an l or r value reference.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524