Home > Net >  Why does the cast operator attempt to call the constructor first?
Why does the cast operator attempt to call the constructor first?

Time:10-28

I have an object that stores some state in a member of type S. Upon request of the view method, it returns a view of it of the type T, obtaining it by casting.

template<typename S, typename T>
struct Viewer {
    S m_state;
    Viewer(const S& state): m_state(state) {}
    auto view() {
        // return m_state.operator T();
        return static_cast<T>(m_state);
    }
};

int main() {
    Viewer<double, double> v(1.);
    std::cout << v.view() << std::endl;
}

Let's suppose that the object is made like this:

#include <tuple>
struct State: std::tuple<int, int> {
    template<typename T>
    State(const T& t) { std::get<0>(*this) = std::get<1>(*this) = t; }
    template<typename T>
    State(const T& t1, const T& t2) {
        std::get<0>(*this) = t1;
        std::get<1>(*this) = t2;
    }
};
std::ostream& operator<<(std::ostream& os, const State& s) {
    return os << std::get<0>(s) << " " << std::get<1>(s);
}

struct Wrapper {
    State m_state;
    Wrapper(const State& initial_state): m_state(initial_state) {}
    operator State() const { return m_state; }  // Conversion operator
};


int main() {
    State s(0, 1);
    Wrapper w(s);
    Viewer<Wrapper, State> v(w);
    std::cout << v.view() << std::endl;  // <<< Error
}

By compiling this code now I get the error

error: cannot convert ‘const Wrapper’ to ‘std::__tuple_element_t<1, std::tuple<int, int> >’ {aka ‘int’} in assignment
State(const T& t) { std::get<0>(*this) = std::get<1>(*this) = t; }

Basically static_cast<T>(m_state) is trying to perform the cast by calling the constructor of the object of type T (with T=State), passing to it m_state (which is of type Wrapper) as the only argument. Since T has a template constructor that takes a single argument, it gets called. However the assignment fails because that constructor was not intended to work with an object of type Wrapper. By dropping the constructor template<typename T> State(const T& t); from the State class the problem gets solved, because static_cast can no longer find a valid constructor to call. However, I need that constructor, and I cannot simply remove it.

If I explicitly call the cast operator in the Viewer::view method, the code runs again

    return m_state.operator T();

However, it no longer works for a Viewer<double, double> object, because double does not have the member operator double

error: request for member ‘operator double’ in ‘((Viewer<double, double>*)this)->Viewer<double, double>::m_state’, which is of non-class type ‘double’

And I also need the Viewer class to work by instantiating it with <double, double>.

I don't want to specialize the State class constructor, because it should be unaware of the Wrapper class: what if a second wrapper class was created? Would it be necessary to specialize the state constructor further? Also, I prefer not to have specializations for the Viewer class. Again, what would happen if you created multiple wrapper classes? Or if you used it with an object without an explicit conversion operator?

I think the best solution is that the casting operation does not try to call the constructor when a suitable operator is available, however I have no idea how to do that. How can I solve this problem?

CodePudding user response:

You don't have to specialize the State constructor, but do something else to SFINEA out the constructor template so that the default copy constructor is called in this case.

struct State: std::tuple<int, int> {
    template<typename T,
             typename = std::enable_if_t<std::is_convertible_v<T, int>>>
    State(const T& t) {
        std::get<0>(*this) = std::get<1>(*this) = t;
    }
};

Demo

Without the std::enable_if part, the constructor template is instantiated with T=Wrapper when resolution begins. State(const Wrapper&) is added to the candidate set as a non-template function and is preferred over the predefined copy or move constructor that you expect.

  • Related