Reputation: 2648
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.
Upvotes: 3
Views: 96
Reputation: 217275
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())
{/*...*/}
};
Upvotes: 1
Reputation: 30831
You're trying to infer a two-step conversion, but:
You can make the given code work by introducing a temporary, either explicitly:
variable<int> a;
member_variable<float> b;
expression<int> c = a;
b = c;
or inline:
int main() {
variable<int> a;
member_variable<float> b;
b = expression<int>(a);
}
Or, you might overload the operator=
to perform the conversion:
template <typename U>
auto& operator=(const expression<U>&) {
return *this;
}
template <typename U>
auto& operator=(const variable<U>& other) {
return operator=(expression<U>(other));
}
Upvotes: 1
Reputation: 385144
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.
Upvotes: 7