Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I properly declare a template friend operator overload to a template class without defining it inline

I'm working in C++20 but without concepts. I have a integer type

template<typename T>
class profile_integer;

for which I wish to define a multiplication operator on

   friend constexpr profile_integer<T> operator*<T>(const profile_integer<T>& lhs, const profile_integer<T>& rhs);

Good so far, but now I want to be able to handle multiplication between different types and since this is a template I can't do implicit conversions; so fine, I can define another operator (using std::complex as an example)

   friend constexpr typename std::enable_if<! std::is_same<profile_integer<T>, V>::value, profile_integer<T>>::type operator*(const V& lhs, const profile_integer<T>& rhs);

Now, apologies for the return type; since V could be profile_integer<T> I needed this.

and I define my operators

template<typename T>
constexpr profile_integer<T> operator*(const profile_integer<T>& lhs, const profile_integer<T>& rhs)
{
   profile_integer<T> x;
   x.value = lhs.value * rhs.value;
   return x;
}

template<typename T, typename V> 
constexpr typename std::enable_if<!std::is_same<profile_integer<T>, V>::value, profile_integer<T>>::type operator*(const V& lhs, const profile_integer<T>& rhs)
{
   profile_integer<T> x;
   x.value = rhs.value * lhs;
   return x;
}

But now I have a problem, for my second friend declaration I have the error

inline function 'operator*<int>' is not defined

For the first declaration I was able to fix this by declaring the friend operator*<T> which I believe specialized on T. For the second declaration any of operator*<...> with any combination of T and V produces the error

function template partial specialization is not allowed

I have a full copy of a minimal example at Compiler Explorer here. I can resolve this issue by defining the friend operator overloads inside the class definition however I'm wondering if there's a way to resolve this without doing that as I'd like to understand what's going on here.

The full example is

#include <map>
#include <type_traits>

template<typename T>
class profile_integer;

template<typename T>
constexpr profile_integer<T> operator*(const profile_integer<T>& lhs, const profile_integer<T>& rhs);

template<typename T, typename V>
constexpr typename std::enable_if<! std::is_same<profile_integer<T>, V>::value, profile_integer<T>>::type operator*(const V& lhs, const profile_integer<T>& rhs);

template<typename T>
class profile_integer 
{
private:
   T value; 

public:
   profile_integer() : value{0} {}

   template<typename V>
   constexpr profile_integer(V x) : value{x} {}


   friend constexpr profile_integer<T> operator*<T>(const profile_integer<T>& lhs, const profile_integer<T>& rhs);

   template<typename V> 
   friend constexpr typename std::enable_if<! std::is_same<profile_integer<T>, V>::value, profile_integer<T>>::type operator*(const V& lhs, const profile_integer<T>& rhs);
};

template<typename T>
constexpr profile_integer<T> operator*(const profile_integer<T>& lhs, const profile_integer<T>& rhs)
{
   profile_integer<T> x;
   x.value = lhs.value * rhs.value;
   return x;
}

template<typename T, typename V> 
constexpr typename std::enable_if<!std::is_same<profile_integer<T>, V>::value, profile_integer<T>>::type operator*(const V& lhs, const profile_integer<T>& rhs)
{
   profile_integer<T> x;
   x.value = rhs.value * lhs;
   return x;
}

int main()
{
    profile_integer<int> x;
    x = x * x;
    x = 2*x;
    return 0;
}

Edit: The C++20 standard 13.9.2(c) notes that constexpr may not be used with explicit template specialization. Removing the constexpr allows the code to compile but linking still fails when operator*(...) does not include the <>. Use of <> still results in a partial template specialization error.

So after digging through the standard and not coming up with anything I tried this

I define a new function

template<typename T, typename V>
profile_integer<T> mul(const V& lhs, const profile_integer<T>& rhs)
{
   return lhs * rhs.get_value();
}

I can call this function

int main()
{
   profile_integer<int> x;
   x = x * x;
   x = mul(2, x);
   return 0;
}

And this works

Now if I simply declare the function a friend in the class declaration

   template<typename V>
   friend profile_integer<T> mul(const V& lhs, const profile_integer<T>& rhs);

This compiles but fails to link.

like image 940
Michael Conlen Avatar asked Oct 17 '25 13:10

Michael Conlen


1 Answers

Your operator is a template with two template arguments, hence the friend declaration reads:

template<typename U,typename V> 
friend constexpr typename std::enable_if_t<!std::is_same_v<profile_integer<U>, V>, profile_integer<U>> operator*(const V&, const profile_integer<U>&);

Note the use of the helper template _v and _t for less verbosity.

Live Demo


In your code there are 2 distinct template arguments that are both called T. You want them to be equal for the friend operator. You want to befriend only a partial specialization of that U == T and V is any type. I admit, I don't know if this is possible. You cannot partially specialize a function template.

You can wrap the implementation in a class template and befriend that. At this point, I am not sure anymore if it's an advantage to not directly define the operator inline with the friend declaration. However, this is how it can be done:

#include <cstdint>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <type_traits>

template<typename T>
class profile_integer;

template<typename T>
constexpr profile_integer<T> operator*(const profile_integer<T>&, const profile_integer<T>&);

template<typename T, typename V>
constexpr typename std::enable_if<! std::is_same<profile_integer<T>, V>::value, profile_integer<T>>::type operator*(const V& lhs, const profile_integer<T>& rhs);

template <typename T>
struct mult {
    template <typename V> constexpr typename std::enable_if<! std::is_same<profile_integer<T>, V>::value, profile_integer<T>>::type m(const V& lhs, const profile_integer<T>& rhs) {
        profile_integer<T> x;
        x.value = rhs.value * lhs;
        return x;
    }
};

template<typename T>
class profile_integer 
{
private:
   T value; 

public:
   profile_integer() : value{0} {}

   template<typename V>
   constexpr profile_integer(V x) : value{x} {}


   friend constexpr profile_integer<T> operator*<T>(const profile_integer<T>& lhs, const profile_integer<T>& rhs);

   friend mult<T>;

};

template<typename T>
constexpr profile_integer<T> operator*(const profile_integer<T>& lhs, const profile_integer<T>& rhs)
{
   profile_integer<T> x;
   x.value = lhs.value * rhs.value;
   return x;
}

template<typename T, typename V> 
constexpr typename std::enable_if<!std::is_same<profile_integer<T>, V>::value, profile_integer<T>>::type operator*(const V& lhs, const profile_integer<T>& rhs)
{
    return mult<T>{}.m(lhs,rhs);
}

int main()
{
    profile_integer<int> x;
    x = x * x;
    x = 2*x;
    return 0;
}
like image 166
463035818_is_not_a_number Avatar answered Oct 19 '25 03:10

463035818_is_not_a_number