This question is best illustrated by example
template <typename T>
struct expression {
};
template <typename T>
struct variable {
operator expression<T>() const {
return {};
}
};
template <typename T>
struct member_variable {
template <typename U>
void operator=(const expression<U>&) {}
};
int main() {
variable<int> a;
member_variable<float> b;
b=a;
}
As it stands, the assignment operator cannot be used because there are issues with deducing U (at least I believe that is what the errors are telling me). How can I make the code compile? I have also tried creating a converting constructor for expression that takes variable and that did not work either. I would like to avoid inheriting from expression, as it is a more heavy class in practice than the other two.
The operator=* is a stand in for other advanced usages, such as adding operator*(expression<T>, expression<U>) and being able to invoke them with a*b.
I've tried Clang trunk (8.0.0) and GCC trunk (9.0.0) with -std=c++17, and MSVC 15.9.3.
Clang message:
prog.cc:28:6: error: no viable overloaded '='
b=a;
~^~
prog.cc:20:8: note: candidate function (the implicit copy assignment operator) not viable: no known conversion from 'variable<int>' to 'const member_variable<float>' for 1st argument
struct member_variable {
^
prog.cc:20:8: note: candidate function (the implicit move assignment operator) not viable: no known conversion from 'variable<int>' to 'member_variable<float>' for 1st argument
struct member_variable {
^
prog.cc:22:10: note: candidate template ignored: could not match 'expression' against 'variable'
void operator=(const expression<U>&) {}
^
1 error generated.
GCC message:
prog.cc: In function 'int main()':
prog.cc:28:7: error: no match for 'operator=' (operand types are 'member_variable<float>' and 'variable<int>')
28 | b=a;
| ^
prog.cc:22:10: note: candidate: 'template<class U> void member_variable<T>::operator=(const expression<U>&) [with U = U; T = float]'
22 | void operator=(const expression<U>&) {}
| ^~~~~~~~
prog.cc:22:10: note: template argument deduction/substitution failed:
prog.cc:28:7: note: 'variable<int>' is not derived from 'const expression<T>'
28 | b=a;
| ^
prog.cc:20:8: note: candidate: 'constexpr member_variable<float>& member_variable<float>::operator=(const member_variable<float>&)'
20 | struct member_variable {
| ^~~~~~~~~~~~~~~
prog.cc:28:7: note: no known conversion for argument 1 from 'variable<int>' to 'const member_variable<float>&'
28 | b=a;
| ^
prog.cc:20:8: note: candidate: 'constexpr member_variable<float>& member_variable<float>::operator=(member_variable<float>&&)'
20 | struct member_variable {
| ^~~~~~~~~~~~~~~
prog.cc:28:7: note: no known conversion for argument 1 from 'variable<int>' to 'member_variable<float>&&'
28 | b=a;
| ^
* As pointed out, usually operator= returns T&, however my use case for this class is (at least for right now) to not allow chaining.
You're trying to invoke a function template instantiation that takes an expression<U> after deducing U. There is no U to deduce, though, because you're not passing an expression<U>. You're passing a variable<int>. It is true that variable<int> can be converted to expression<int>, but you're not triggering that. Deduction fails before conversion is attempted (because how could it be deduced from a totally different type?).
For a quick fix, b=expression<int>(a) should solve it. You could consider making a decay() function that does this for you, for what are effectively your own kind of lvalue-to-rvalue conversions! That's probably about as terse as you can make it without further architectural changes.
Beyond that, I don't have a concrete solution for you, other than to say that you will need to rethink this class design according to your requirements.
Instead of (or in addition to) implicit conversion I propose function for conversion:
template <typename T> struct expression {/**/};
template <typename T> struct variable {/**/};
template <typename T>
const expression<T>& as_expression(const expression<T>& e) { return e;}
template <typename T> expression<T> as_expression(const variable<T>&) {/**/}
And then use SFINAE on as_expression, something like:
template <typename T>
struct member_variable {
template <typename U>
auto operator=(const U& u)
-> decltype(as_expression(u), void())
{/*...*/}
};
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With