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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With