Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I implictly convert one template class to another?

Tags:

c++

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.

like image 558
user975989 Avatar asked Dec 08 '25 11:12

user975989


2 Answers

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.

like image 50
Lightness Races in Orbit Avatar answered Dec 10 '25 00:12

Lightness Races in Orbit


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

like image 42
Jarod42 Avatar answered Dec 10 '25 00:12

Jarod42



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!