c++ - How do I properly declare a template friend operator overload to a template class without defining it inline - Stack Overf

I'm working in C++20 but without concepts. I have a integer typetemplate<typename T>class

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.

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.

Share edited Mar 8 at 20:21 Michael Conlen asked Mar 7 at 21:46 Michael ConlenMichael Conlen 2,1383 gold badges18 silver badges20 bronze badges 2
  • Looks very confusing to me. Why do you want to define 2*x but not x*2? When you multiply profile_integer<int> by profile_integer<long>, what is the return type and why? – n. m. could be an AI Commented Mar 7 at 22:32
  • I would eventually write the operator for x*2 as well but this was the first one I did and was stuck. For this use case multiplying profile_integer<T> by profile_integer<U> for any distinct types doesn't really make sense; the actual use case is some unlimited precision integer and what I'm profiling is the number of multiplies of various sizes so I can compare different algorithms use of large multiplies. I can shoehorn any number of things to make it work but I wanted to understand the particular C++ issues I'm running into. – Michael Conlen Commented Mar 8 at 1:02
Add a comment  | 

2 Answers 2

Reset to default 2

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;
}

When you declare

template<typename T>
class profile_integer;

and have

   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);

inside of of the class definition and you instantiate a profile_integer<T> for some concrete type T that declares a friend function operator*<V> rather than operator*<T, V> . So when we define an out of line function on <T, V> that function is not a friend. This is because template parameters are part of the function signature.

This is the reason for the code compiling and linking without the friend declaration; it finds the defined function. When you add the friend declaration the overload resolution picks the friend declaration but that's not what's defined.

So the answer is that we can't do what was asked for (but now we know why). The options are to provide a specialization, define it inline or provide a public interface so that the operator need not be a friend.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744908756a4600417.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信