Azad
Azad

Reputation: 1120

Declare a template function taking two objects of a template class friend to (only) those two specializations

I have a template class Test (with an integer as template argument) and a template function (in this case operator*) that takes two objects of Test class with possibly different template arguments. The function needs to be friend to both its arguments. Here is a minimally working example:

#include <type_traits>

template <int N>
class Test;

template <int N1, int N2>
Test<N1+N2> operator* (Test<N1>, Test<N2>);

template <int N>
class Test {
  double val;
public:
  Test (double x) : val{x} {}
  template <int N1, int N2>
  friend Test<N1+N2> operator* (Test<N1>, Test<N2>);
};

template <int N1, int N2>
Test<N1+N2> operator* (Test<N1> x, Test<N2> y) {
  return Test<N1+N2> {x.val*y.val};
}

int main (int argc, char* argv[]) {
  Test<1> a{4.}, c{7.9};
  Test<2> b{3.5};
  a*b;
  a*c;
  return 0;
}

This works, but the function is friend to every specializations of Test. I want to make it to be friend only with Test<N1> and Test<N2>.

I tried declaring it like this:

template <int N>
class Test {
  double val;
public:
  Test (double x) : val{x} {}
  template <int N1, int N2>
  friend std::enable_if_t<N1==N||N2==N,Test<N1+N2>> operator* (Test<N1>, Test<N2>);
};

but encountered g++ errors for ambiguous overloads. I also tried:

template <int N>
class Test {
  double val;
public:
  Test (double x) : val{x} {}
  template <int N1, int N2, typename = std::enable_if_t<N1==N||N2==N>>
  friend Test<N1+N2> operator* (Test<N1>, Test<N2>);
};

but default template arguments are not allowed for friend declarations.

I prefer a solution in C++14 but solutions in C++17 would also be acceptable.

Update: Following the answer of S.M. I suggest the following solution to those with similar problems

template <int N>
class Test {
  double val;
public:
  Test (double x) : val{x} {}

  template <int N2>
  Test<N+N2> operator* (Test<N2> y) {
    return Test<N+N2> {val*y.val};
  }

  template <int N2>
  friend class Test;
};

int main (int argc, char* argv[]) {
  Test<1> a{4.}, c{7.9};
  Test<2> b{3.5};
  a*b;
  a*c;
  return 0;
}

Upvotes: 3

Views: 593

Answers (2)

3CxEZiVlQ
3CxEZiVlQ

Reputation: 38773

I offer the solution bellow. The function is not a friend function any more.

#include <type_traits>

template <int N>
class Test {
  double val;
public:
  Test (double x) : val{x} {}

  template <int N2>
  Test<N+N2> mul(const Test<N2> &y) const {
    return Test<N+N2>{val * y.val};
  }

  template <int N2>
  friend class Test;
};

template <int N1, int N2>
Test<N1+N2> operator* (const Test<N1> &x, const Test<N2> &y) {
  return Test<N1+N2>{x.template mul<N2>(y)};
}

int main (int argc, char* argv[]) {
  Test<1> a{4.}, c{7.9};
  Test<2> b{3.5};
  a*b;
  a*c;
  return 0;
}

As a member operator *:

#include <type_traits>

template <int N>
class Test {
  double val;
public:
  Test (double x) : val{x} {}

  template <int N2>
  Test<N+N2> operator *(const Test<N2> &y) const {
    return Test<N+N2>{val * y.val};
  }

  template <int N2>
  friend class Test;
};

int main (int argc, char* argv[]) {
  Test<1> a{4.}, c{7.9};
  Test<2> b{3.5};
  a*b;
  a*c;
  return 0;
}

Upvotes: 1

Yola
Yola

Reputation: 19031

No, you can't do this.

17.5.4 Friends [temp.friend]

A friend of a class or class template can be a function template or class template, a specialization of a function template or class template, or a non-template function or class.

Out of these 6 options you might be interesting in function template.

So you can befriend the operator in three ways

template <int N1, int N2>
friend Test<N1+N2> operator* (Test<N1>, Test<N2>); #1

template <int N2>
friend Test<N + N2> operator* (Test<N>, Test<N2>); #2

template <int N1>
friend Test<N1 + N> operator* (Test<N1>, Test<N>); #3

Option #1 you have tried and it doesn't work as you would like it to. Options #2 and #3 also won't work for two reasons: first, you would need to define this overload somewhere, second, if you define such a overload it would not work with second parameter (#2) and with first parameter (#3).

Upvotes: 0

Related Questions