1

I have the following variant:

typedef boost::variant<int, float, bool> TypeVariant;

And I want to create a visitor to convert a int or float type to a bool type.


struct ConvertToBool : public boost::static_visitor<TypeVariant> {

    TypeVariant operator()(int a) const {
        return (bool)a;
    }

    TypeVariant operator()(float a) const {
        return (bool)a;
    }
};

However this is giving me the error message:

'TypeVariant ConvertToBool::operator ()(float) const': cannot convert argument 1 from 'T' to 'float'

What is the correct way of allowing a visitor to only apply to certain types?

Tom
  • 1,235
  • 9
  • 22

1 Answers1

1

Just include the missing overload:

Live On Coliru

#include <boost/variant.hpp>
#include <iostream>

using TypeVariant = boost::variant<int, float, bool>;

struct ConvertToBool {
    using result_type = TypeVariant;
    TypeVariant operator()(TypeVariant const& v) const {
        return boost::apply_visitor(*this, v);
    }

    TypeVariant operator()(int   a) const { return a != 0; }
    TypeVariant operator()(float a) const { return a != 0; }
    TypeVariant operator()(bool  a) const { return a; }
} static constexpr to_bool{};

int main() {
    using V = TypeVariant;
    
    for (V v : {V{}, {42}, {3.14f}, {true}}) {
        std::cout << v << " -> " << std::boolalpha << to_bool(v) << "\n";
    }
}

Generalize

In more general cases you can supply a catch-all template overload:

template <typename T> TypeVariant operator()(T const& a) const {
    return static_cast<bool>(a);
}

In fact in your trivial case that's all you needed anyways:

Live On Coliru

#include <boost/variant.hpp>
#include <iostream>

using TypeVariant = boost::variant<int, float, bool>;

struct ConvertToBool {
    using result_type = TypeVariant;
    TypeVariant operator()(TypeVariant const& v) const {
        return boost::apply_visitor(*this, v);
    }

    template <typename T> TypeVariant operator()(T const& a) const {
        return static_cast<bool>(a);
    }
} static constexpr to_bool{};

int main() {
    using V = TypeVariant;

    for (V v : { V{}, { 42 }, { 3.14f }, { true } }) {
        std::cout << v << " -> " << std::boolalpha << to_bool(v) << "\n";
    }
}

Still prints

0 -> false
42 -> true
3.14 -> true
true -> true
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you so much for the detailed answer. Yes this bool converting example is just a simplified problem to my real problem that was answered perfectly by these examples. I have a additional question in relation to your first example: When I add `std::string` to the variant, and fail to specify the operator overload for it, when passing std::string to `to_bool`, it returns a true. Why is this the case? I would expect it to crash since there is no overload specified. – Tom Nov 29 '20 at 20:41
  • I'd expect it to not compile, not crash. But apparently, yes there's a loophole that causes it to be [UB](https://en.wikipedia.org/wiki/Undefined_behavior): See it **[SEGV on Coliru](http://coliru.stacked-crooked.com/a/875fb280de47cdea)**. Enable asan/ubsan [shows what's going on](http://coliru.stacked-crooked.com/a/2015041e0c590187): It's simply repeatedly hitting the `VariantType` overload. I didn't previously realize this is a potential bug source with that approach (of doing `apply_visitor` inside the calleable type). – sehe Nov 29 '20 at 21:28
  • I usually have it set up like this: http://coliru.stacked-crooked.com/a/8efbacc6526acad6 - the separate (private and potentially static) `call` implementations [prevent the infinite recursion](http://coliru.stacked-crooked.com/a/8307ce12de89fdeb) [now it doesn't compile]. Besides this idiom makes it elegant to implement visitors over recursive variants because e.g. `return call(x) + call(y)` looks better than `return operator()(x) + operator()(y)` or even worse `return (*this)(x) + (*this)(y);` – sehe Nov 29 '20 at 21:31