简体   繁体   中英

Boost Variant conversion error when using visitor

I don't mean to code dump but this is genuinely the smallest reproducible example of this I could create even after removing all the logic to make it clearer.

Essentially I'm trying to implement my own version of some of the base types in C++ to mimic dynamic typing by storing the types in a boost::variant called Values and using boost::static_visitor s to perform operations on the Value variant. One operation I'm trying to implement is the the not equals operator, and I have created a visitor called Not_Equal to achieve this. The Not_Equal operator uses SFINAE with a low_priority and high_priority struct to determine if the two types used in the operation are allowed.

The types in the Values variant are: { SpecialInt , SpecialBoolean , std::shared_ptr<SeriesInt> , std::shared_ptr<SeriesBoolean> }. The reason why the SeriesInt and SeriesBoolean are smart pointers is because they store a lot of information in my real version so copying them will be expensive.

The accepted:= operators are as follows:

SpecialInt != SpecialInt
SpecialBoolean != SpecialBoolean
SeriesInt != SpecialInt
SeriesInt != SeriesInt
SeriesBoolean != SeriesBoolean

as represented by the operator overloads in each class.

#include <memory>
#include <boost/variant/static_visitor.hpp>
#include <boost/variant.hpp>

class SpecialBoolean  {
public:

    SpecialBoolean(bool val) {} //removing this line fixes it
    SpecialBoolean()  {}

    SpecialBoolean operator!= (const SpecialBoolean& rhs) const {
        return *this;
    }

};

class SpecialInt {
public:

    SpecialInt(float val) {} //removing this line fixes it
    SpecialInt() {}

    SpecialBoolean operator!= (const SpecialInt& rhs) const {
        return SpecialBoolean();
    }

};

class SeriesBoolean {
public:

    SeriesBoolean() {}

    std::shared_ptr<SeriesBoolean> operator!= (const SpecialBoolean& rhs) const {
        return std::make_shared<SeriesBoolean>();
    }
    std::shared_ptr<SeriesBoolean> operator!= (const SeriesBoolean& rhs) const {
        return std::make_shared<SeriesBoolean>();
    }

};

class SeriesInt {
public:
    
    SeriesInt() {}
    
    std::shared_ptr<SeriesBoolean> operator!= (const SpecialInt& rhs) const {
        return std::make_shared<SeriesBoolean>();
    }
    std::shared_ptr<SeriesBoolean> operator!= (const SeriesInt& rhs) const {
        return std::make_shared<SeriesBoolean>();
    }

};

typedef boost::variant <SpecialInt, SpecialBoolean, std::shared_ptr<SeriesInt>, std::shared_ptr<SeriesBoolean> > Values;

struct low_priority {};
struct high_priority : low_priority {};

struct Not_Equal : public boost::static_visitor<Values> {

    auto operator() (Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    template <typename T, typename U>
    auto operator() (high_priority, T a, U b) const -> decltype(Values(a != b)) {
        return a != b; // problem here
    }

    template <typename T, typename U>
    auto operator() (high_priority, std::shared_ptr<T> a, std::shared_ptr<U> b) const -> decltype(Values(*a != *b)) {
        return *a != *b;
    }

    template <typename T, typename U>
    auto operator() (high_priority, std::shared_ptr<T> a, U b) const -> decltype(Values(*a != b)) {
        return *a != b;
    }

    template <typename T, typename U>
    Values operator() (low_priority, T, U) const {
        throw std::runtime_error("Incompatible arguments");
    }

    template <typename T, typename U>
    Values operator() (T a, U b) const {
        return (*this)(high_priority{}, a, b);
    }
};

The problem occurs in the visitor at line return a;= b;, where the operator types are not shared_ptr's and therefore either SpecialInt or SpecialBoolean which causes the errors:

Error C2446 ':=': no conversion from 'SeriesInt *' to 'SeriesBoolean *'

Error C2446 ':=': no conversion from 'SeriesBoolean *' to 'SeriesInt *'

I don't understand what it has to do with SeriesBoolean* or SeriesInt* since it can only accept types of SpecialInt and SpecialBoolean , but I've noticed that when I remove the constructors which take a argument in SpecialInt and SpecialBoolean that the code compiles and runs as normal. I need those constructors to load values into the classes (logic removed) so my question is why am I getting these errors and how can I fix this?

The contructors for your types lead to ambiguous variant initializers.

If you can, consider making them explicit.

Also, the decltype return types don't really make sense since the visitor returns Values by definition.

This overload

template <typename T, typename U>
Values operator()(high_priority, T a, U b) const {
    return a != b; // problem here
}

matches ALL combinations. operator != is /not defined/ in such cases. Did you mean to do:

template <typename T>
Values operator()(high_priority, T const& a, T const& b) const {
    return a != b; // problem here
}

Now you need the following overload as well:

template <typename T>
Values operator()(high_priority, std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
    return *a != *b;
}

Otherwise two identical shared_pointer argument types would be ambiguous.

This overload seems wrong:

template <typename T, typename U>
Values operator()(high_priority, std::shared_ptr<T> const& a, std::shared_ptr<U> const& b) const {
    return *a != *b;
}

This will obviously lead to problems because it will eg compare SeriesInt to SeriesBool which isn't implemented. Since it's not on your list, drop it.

Likewise, since

template <typename T, typename U>
Values operator()(high_priority, std::shared_ptr<T> const& a, U const& b) const {
    return *a != b;
}

also matches eg [ T = SeriesInt, U = SpecialBoolean ], it will not compile.

SIMPLIFY!

I would basically run down the list of supported overloads and just explicitly implement them. I'll use the above templates only for the cases that are 1:1.

Note that consistently (.) taking args by const& make the execution a lot more efficient especially for the shared pointers.

struct Not_Equal : boost::static_visitor<Values> {
    Values operator()(Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    // SpecialInt     != SpecialInt
    // SpecialBoolean != SpecialBoolean
    template <typename T>
    Values operator()(T const& a, T const& b) const {
        return a != b;
    }

    // SeriesInt      != SeriesInt
    // SeriesBoolean  != SeriesBoolean
    template <typename T>
    Values operator()(std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
        return *a != *b;
    }

    // SeriesInt      != SpecialInt
    Values operator()(std::shared_ptr<SeriesInt> const& a, SpecialInt const& b) const {
        return *a != b;
    }

    template <typename... T>
    Values operator()(T const&...) const {
        throw std::runtime_error("Incompatible arguments");
    }
};

Live Demo

Live On Coliru

#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <memory>

struct SpecialBoolean {
    explicit SpecialBoolean(bool /*val*/ = false) {}

    SpecialBoolean operator!=(const SpecialBoolean& /*rhs*/) const { return *this; }
};

struct SpecialInt {
    explicit SpecialInt(float /*val*/ = 0) {}

    SpecialBoolean operator!=(const SpecialInt& /*rhs*/) const {
        return SpecialBoolean();
    }
};

struct SeriesBoolean {
    SeriesBoolean() {}

    std::shared_ptr<SeriesBoolean> operator!=(const SpecialBoolean& /*rhs*/) const {
        return std::make_shared<SeriesBoolean>();
    }
    std::shared_ptr<SeriesBoolean> operator!=(const SeriesBoolean& /*rhs*/) const {
        return std::make_shared<SeriesBoolean>();
    }
};

struct SeriesInt {
    SeriesInt() {}

    std::shared_ptr<SeriesBoolean> operator!=(const SpecialInt& /*rhs*/) const {
        return std::make_shared<SeriesBoolean>();
    }
    std::shared_ptr<SeriesBoolean> operator!=(const SeriesInt& /*rhs*/) const {
        return std::make_shared<SeriesBoolean>();
    }
};

typedef boost::variant<
    SpecialInt,
    SpecialBoolean,
    std::shared_ptr<SeriesInt>,
    std::shared_ptr<SeriesBoolean>
  >
  Values;

struct Not_Equal : boost::static_visitor<Values> {
    Values operator()(Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    // SpecialInt     != SpecialInt
    // SpecialBoolean != SpecialBoolean
    template <typename T>
    Values operator()(T const& a, T const& b) const {
        return a != b;
    }

    // SeriesInt      != SeriesInt
    // SeriesBoolean  != SeriesBoolean
    template <typename T>
    Values operator()(std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
        return *a != *b;
    }

    // SeriesInt      != SpecialInt
    Values operator()(std::shared_ptr<SeriesInt> const& a, SpecialInt const& b) const {
        return *a != b;
    }

    template <typename... T>
    Values operator()(T const&...) const {
        throw std::runtime_error("Incompatible arguments");
    }
};

int main() {
}

BONUS

Also, I think you should encapsulate the optimization of using shared_ptr<>, eliminating all your special cases.

This simplifies all of the above to:

struct Not_Equal : boost::static_visitor<Values> {
    Values operator()(Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    template <typename T>
    Values operator()(T const& a, T const& b) const { return a != b; }
    Values operator()(SeriesInt const& a, SpecialInt const& b) const { return a != b; }
    Values operator()(SeriesBoolean const& a, SpecialBoolean const& b) const { return a != b; }

    template <typename... T>
    Values operator()(T const&...) const {
        throw std::runtime_error("Incompatible arguments");
    }
};

Here's a complete demo with testcases for that Live On Coliru

#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <memory>
#include <vector>
#include <iostream>
#include <iomanip>

struct SpecialBoolean {
    explicit SpecialBoolean(bool val = false) : val(val) {}
    SpecialBoolean operator!=(const SpecialBoolean& rhs) const { return SpecialBoolean{val != rhs.val}; }
  private:
    bool val;
    friend std::ostream& operator<<(std::ostream& os, SpecialBoolean const& b) { return os << "SpecialBoolean{" << std::boolalpha << b.val << "}"; }
};

struct SpecialInt {
    explicit SpecialInt(float val = false) : val(val) {}
    SpecialBoolean operator!=(const SpecialInt& rhs) const { return SpecialBoolean{val != rhs.val}; }
  private:
    float val;
    friend std::ostream& operator<<(std::ostream& os, SpecialInt const& i) { return os << "SpecialInt{" << i.val << "}"; }
};

struct SeriesBoolean {
    SeriesBoolean operator!=(const SpecialBoolean& /*rhs*/) const { return {}; } // TODO
    SeriesBoolean operator!=(const SeriesBoolean& /*rhs*/) const { return {}; } // TODO

  private:
    struct VeryLarge {
        std::array<SpecialBoolean, 512> _it_is;
    };
    std::shared_ptr<VeryLarge> _data = std::make_shared<VeryLarge>();
    friend std::ostream& operator<<(std::ostream& os, SeriesBoolean const&) { return os << "SeriesBoolean{...}"; }
};

struct SeriesInt {
    SeriesBoolean operator!=(const SpecialInt& /*rhs*/) const { return {}; }
    SeriesBoolean operator!=(const SeriesInt& /*rhs*/) const { return {}; }

  private:
    struct VeryLarge {
        std::array<SpecialInt, 512> _it_is;
    };
    std::shared_ptr<VeryLarge> _data = std::make_shared<VeryLarge>();
    friend std::ostream& operator<<(std::ostream& os, SeriesInt const&) { return os << "SeriesInt{...}"; }
};

using Values = boost::variant< SpecialInt, SpecialBoolean, SeriesInt, SeriesBoolean >;

struct Not_Equal : boost::static_visitor<Values> {
    Values operator()(Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    template <typename T>
    Values operator()(T const& a, T const& b) const { return a != b; }
    Values operator()(SeriesInt const& a, SpecialInt const& b) const { return a != b; }
    Values operator()(SeriesBoolean const& a, SpecialBoolean const& b) const { return a != b; }

    template <typename... T>
    Values operator()(T const&...) const {
        throw std::runtime_error("Incompatible arguments");
    }
};

int main() {
    Values const vv[] = {
        SpecialInt(42),
        SpecialInt(-314e-2),
        SpecialBoolean(false),
        SpecialBoolean(true),
        SeriesInt(),
        SeriesBoolean()
    };

    Not_Equal const neq;

    auto col = [](auto const& v, bool right = false) -> auto& {
        std::ostringstream ss; // just for quick formatting
        ss << v;

        if (right)
            std::cout << std::right;
        else
            std::cout << std::left;
        return std::cout << std::setw(21) << ss.str();
    };

    for (auto const& a: vv) for (auto const& b: vv) try {
        col(a, true) << " != ";
        col(b) << " --> ";
        col(neq(a, b)) << "\n";
    } catch(std::exception const& e) {
        col(e.what()) << "\n";
    }
}

Printing

       SpecialInt{42} != SpecialInt{42}        --> SpecialBoolean{false}
       SpecialInt{42} != SpecialInt{-3.14}     --> SpecialBoolean{true} 
       SpecialInt{42} != SpecialBoolean{false} --> Incompatible arguments
       SpecialInt{42} != SpecialBoolean{true}  --> Incompatible arguments
       SpecialInt{42} != SeriesInt{...}        --> Incompatible arguments
       SpecialInt{42} != SeriesBoolean{...}    --> Incompatible arguments
    SpecialInt{-3.14} != SpecialInt{42}        --> SpecialBoolean{true} 
    SpecialInt{-3.14} != SpecialInt{-3.14}     --> SpecialBoolean{false}
    SpecialInt{-3.14} != SpecialBoolean{false} --> Incompatible arguments
    SpecialInt{-3.14} != SpecialBoolean{true}  --> Incompatible arguments
    SpecialInt{-3.14} != SeriesInt{...}        --> Incompatible arguments
    SpecialInt{-3.14} != SeriesBoolean{...}    --> Incompatible arguments
SpecialBoolean{false} != SpecialInt{42}        --> Incompatible arguments
SpecialBoolean{false} != SpecialInt{-3.14}     --> Incompatible arguments
SpecialBoolean{false} != SpecialBoolean{false} --> SpecialBoolean{false}
SpecialBoolean{false} != SpecialBoolean{true}  --> SpecialBoolean{true} 
SpecialBoolean{false} != SeriesInt{...}        --> Incompatible arguments
SpecialBoolean{false} != SeriesBoolean{...}    --> Incompatible arguments
 SpecialBoolean{true} != SpecialInt{42}        --> Incompatible arguments
 SpecialBoolean{true} != SpecialInt{-3.14}     --> Incompatible arguments
 SpecialBoolean{true} != SpecialBoolean{false} --> SpecialBoolean{true} 
 SpecialBoolean{true} != SpecialBoolean{true}  --> SpecialBoolean{false}
 SpecialBoolean{true} != SeriesInt{...}        --> Incompatible arguments
 SpecialBoolean{true} != SeriesBoolean{...}    --> Incompatible arguments
       SeriesInt{...} != SpecialInt{42}        --> SeriesBoolean{...}   
       SeriesInt{...} != SpecialInt{-3.14}     --> SeriesBoolean{...}   
       SeriesInt{...} != SpecialBoolean{false} --> Incompatible arguments
       SeriesInt{...} != SpecialBoolean{true}  --> Incompatible arguments
       SeriesInt{...} != SeriesInt{...}        --> SeriesBoolean{...}   
       SeriesInt{...} != SeriesBoolean{...}    --> Incompatible arguments
   SeriesBoolean{...} != SpecialInt{42}        --> Incompatible arguments
   SeriesBoolean{...} != SpecialInt{-3.14}     --> Incompatible arguments
   SeriesBoolean{...} != SpecialBoolean{false} --> SeriesBoolean{...}   
   SeriesBoolean{...} != SpecialBoolean{true}  --> SeriesBoolean{...}   
   SeriesBoolean{...} != SeriesInt{...}        --> Incompatible arguments
   SeriesBoolean{...} != SeriesBoolean{...}    --> SeriesBoolean{...}   

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM