user975989
user975989

Reputation: 2648

How can I implictly convert one template class to another?

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

Answers (3)

Jarod42
Jarod42

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())
    {/*...*/}
};

Demo

Upvotes: 1

Toby Speight
Toby Speight

Reputation: 30831

You're trying to infer a two-step conversion, but:

  • only a single conversion will be considered
  • template matching inhibits implicit conversion.

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

Lightness Races in Orbit
Lightness Races in Orbit

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

Related Questions