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;
}
};
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.