Implicit Conversion When Overloading Operators for Template Classes

Implicit conversion when overloading operators for template classes

The reason it does not just work is that implicit type conversions (that is, via constructors) do not apply during template argument deduction.
But it works if you make the outside operator a friend since then the type T is know, allowing the compiler to investigate what can be casted to make the arguments match.

I made an example based on yours (but removed C++11 stuff), inspired by Item 46 (a rational number class) in Scott Meyers Effective C++ (ed 3). Your question is almost an exact match to that item. Scott also notes that ... "this use of friend is not related to the access of non-public parts of the class."

This will also allow work with mixes of foo< T >, foo< U > etc as long as T and U can be added etc.

Also look at this post: C++ addition overload ambiguity

#include <iostream>

using namespace std;

template< class T >
class foo
{
private:
T _value;
public:
foo() : _value() {}

template <class U>
foo(const foo<U>& that) : _value(that.getval()) {}

// I'm sure this it can be done without this being public also;
T getval() const { return _value ; };

foo(const T& that) : _value(that) {}

friend const foo operator +(foo &lhs,const foo &rhs)
{
foo result(lhs._value+rhs._value);
return result;
};
friend const foo operator +(foo &lhs,const T &rhsval)
{
foo result(lhs._value+rhsval);
return result;
};
friend const foo operator +(const T &lhsval,foo &rhs)
{
foo result(lhsval+rhs._value);
return result;
};

friend foo& operator +=(foo &lhs,const foo &rhs)
{
lhs._value+=rhs._value;
return lhs;
};
friend std::ostream& operator<<(std::ostream& out, const foo& me){
return out <<me._value;
}
};

int main(){
foo< int > f, g;
foo< double > dd;
cout <<f<<endl;
f = f + g;
cout <<f<<endl;
f += 3 ;
cout <<f<<endl;
f = f + 5;
cout <<f<<endl;
f = 7 + f;
cout <<f<<endl;
dd=dd+f;
cout <<dd<<endl;
dd=f+dd;
cout <<dd<<endl;
dd=dd+7.3;
cout <<dd<<endl;
}

c++ implicit conversion on user-defined operator for template classes

I tried to edit Barrys answer with the following (runnable) code, that produces the correct output, but it was rejected there.

I'll add it here in case anyone else is curious.

#include <iostream>

template <int x>
struct A {
int a;
friend int operator+(A a, int b) { return a.a + b; }
};

template <int x>
struct B {
int b;
operator A<x>() { return {b+10}; }
friend int operator+(A<x>, int );
};

int main() {
std::cout << (A<12>{9} + 10) << std::endl;
std::cout << (B<12>{9} + 10) << std::endl;
}

Which prints

19
29

Binary Operator for Template Class Doesn't Resolve Implicit Conversion

The thing you have to remember about templates is that they will not do a conversion for you. All they do is try and figure out the types things are, and if that jives with template parameters, then it will stamp out the function and call it.

In you case when you do

foo_value + 1.0

the compiler goes okay, lets see if we have any operator + that will work for this. It finds

template<typename T>
foo<T> operator+(foo<T> a, foo<T> b)
{
return foo<T>(a.val + b.val);
}

and then it tries to figure out what T is so it can stamp out a concrete function. It looks at foo_value, sees it is a foo<double> so it says for the first parameter T needs to be a double. Then it looks at 1.0 and goes okay, I have a double and that is when you run into a problem. The compiler can't deduce what T should be for b because it expects a foo<some_type>, but got a double instead. Because it can't deduce a type, your code fails to compile.

In order to get the behavior you want you would need to add

template<typename T>
foo<T> operator+(foo<T> a, T b)
{
return foo<T>(a.val + b);
}

Which lets you add a T to a foo<T>, or better yet

template<typename T, typename U>
foo<T> operator+(foo<T> a, U b)
{
return foo<T>(a.val + b);
}

Which lets you add anything to a foo<T> (foo<double> + int for instance where the first version would not allow that)

Operator overloading outside a template class with implicit conversions

template<class T> struct is_wrap : std::false_type {};
template<class T> struct is_wrap<Wrap<T>> : std::true_type {};

template<class T1, class T2> typename std::enable_if<is_wrap<typename std::common_type<T1, T2>::type>::value, bool>::type operator == (const T1& t1, const T2& t2)
{
const typename std::common_type<T1, T2>::type& tc1 = t1, tc2 = t2;
// compare with tc1 and tc2
}

C++ operator overloading and implicit conversion

Assuming you'd like the specialized version to be picked for any integral type (and not just int in particular, one thing you could do is provide that as a template function and use Boost.EnableIf to remove those overloads from the available overload set, if the operand is not an integral type.

#include <cstdio>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>

class CFixed
{
public:
CFixed( int ) {}
CFixed( float ) {}
};

CFixed operator* ( const CFixed& a, const CFixed& )
{ puts("General CFixed * CFixed"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( const CFixed& a, T )
{ puts("CFixed * [integer type]"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( T , const CFixed& b )
{ puts("[integer type] * CFixed"); return b; }

int main()
{
CFixed(0) * 10.0f;
5 * CFixed(20.4f);
3.2f * CFixed(10);
CFixed(1) * 100u;
}

Naturally, you could also use a different condition to make those overloads available only if T=int: typename boost::enable_if<boost::is_same<T, int>, CFixed>::type ...

As to designing the class, perhaps you could rely on templates more. E.g, the constructor could be a template, and again, should you need to distinguish between integral and real types, it should be possible to employ this technique.

Implicit conversion operator for templated types not automatically determined

Template argument deduction doesn't consider implicit conversion.

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

Then given fun(b);, the template fun can't be invoked because T can't be deduced.

You can specify the template argument explicitly, then overload resolution and implicit conversion would work fine.

fun<int>(b);

LIVE

Forcing C++ to prefer an overload with an implicit conversion over a template

If you want to change the overloads such that the former overload is chosen whenever there is an implicit conversion possible, with the latter being the backup, you can do this with SFINAE via std::enable_if:

#include <type_traits>

void call_function(const std::function<void()>& function)
{
std::cout << "CALL FUNCTION 1" << std::endl;
function();
}

template <typename Function,
// Consider this overload only if...
typename std::enable_if<
// the type cannot be converted to a std::function<void()>
!std::is_convertible<const Function&, std::function<void()>>::value,
int>::type = 0>
void call_function(const Function& function)
{
std::cout << "CALL FUNCTION 2" << std::endl;
function();
}

Demo


Alternatively, if you want to be able to support an unknown number of overloads of call_function with the "CALL FUNCTION 2" being a backup overload in case none of the functions work, you can do this too, but it requires quite a bit more work:

// Rename the functions to `call_function_impl`
void call_function_impl(const std::function<void()>& function)
{
std::cout << "CALL FUNCTION 1" << std::endl;
function();
}

void call_function_impl(const std::function<void(int, int)>& function)
{
std::cout << "CALL FUNCTION 2" << std::endl;
function(1, 2);
}

// The backup function must have a distinct name
template <typename Function>
void call_function_backup_impl(const Function& function)
{
std::cout << "CALL FUNCTION backup" << std::endl;
function();
}

// Implement std::void_t from C++17
template <typename>
struct void_impl {
using type = void;
};

template <typename T>
using void_t = typename void_impl<T>::type;

// Make a type trait to detect if the call_function_impl(...) call works
template <typename Function, typename = void>
struct has_call_function_impl
: std::false_type
{};

template <typename Function>
struct has_call_function_impl<Function,
void_t<decltype(call_function_impl(std::declval<const Function&>()))>>
: std::true_type
{};

// If the call_function_impl(...) call works, use it
template <typename Function,
typename std::enable_if<
has_call_function_impl<Function>::value,
int>::type = 0>
void call_function(const Function& function)
{
call_function_impl(function);
}

// Otherwise, fall back to the backup implementation
template <typename Function,
typename std::enable_if<
!has_call_function_impl<Function>::value,
int>::type = 0>
void call_function(const Function& function)
{
call_function_backup_impl(function);
}

Demo



Related Topics



Leave a reply



Submit