3

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;
}
Azad
  • 1,050
  • 8
  • 24
  • This is a strange requirement. So you want those functions which don't satisfy the condition exist but can't access private data ? A reasonable solution is just disable them in the first place. – llllllllll Mar 24 '18 at 17:18
  • I can not understand the requirement. You want the `operator*` being not friend if it is not called? IMHO ambiguous is ok. Removed operator declaration is in one hand and global declaration is in another hand. Compiler can't choose which one to use. – 273K Mar 24 '18 at 17:21
  • Why would `operator*<5, 3>` modify private values of a `Test<6>` in the first place? Why do you need to protect from this? – super Mar 24 '18 at 17:34
  • @liliscent To see why this requirement is needed please see here the problem with approach #1 here https://web.mst.edu/~nmjxv3/articles/templates.html – Azad Mar 24 '18 at 18:43
  • @S.M. please see the above comment – Azad Mar 24 '18 at 18:44
  • @super please see the above comment – Azad Mar 24 '18 at 18:44
  • @super This is a minimally working example. The actual code is part of a library for units and dimensions (hence such multiplication). One can specialize the `operator*` and modify the private data of a global object. I wanted to prevent that but if it's not possible I guess I should prevent against accidents not fraud. – Azad Mar 24 '18 at 19:09
  • Somebody could also specialize `Test` and there’s really no way to prevent it. See http://www.gotw.ca/gotw/076.htm, especially the part at the end about Murphy vs. Machiavelli. – Daniel H Mar 24 '18 at 20:14
  • @DanielH Thanks for the link. I get the point. If we befriend all `Test`s then one could specialize `Test` and gain access as in the update, but before that they would have to copy paste essentially everything (or they change implementation of their global objects). – Azad Mar 24 '18 at 20:43

2 Answers2

1

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;
}
273K
  • 29,503
  • 10
  • 41
  • 64
  • Thank you very much. Could you provide a reference explaining the use of `template` keyword in `x.template mul(y)`. And if we are befriending all specializations of Test to each other (which is reasonable) we can also define the multiplication operator as a member rather than non-member, right? – Azad Mar 24 '18 at 19:59
  • Yes, we can define the operator as a member function. I did not a member as it is in your answer. Look at my update. – 273K Mar 24 '18 at 20:30
  • 1
    The answer to your question regarding using `template` keyword look here https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords – 273K Mar 24 '18 at 20:35
0

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

Yola
  • 18,496
  • 11
  • 65
  • 106
  • 1
    #2 and #3 don't declare specializations. They declare different overloads for each possible `N`, and you would somehow need to define them all. – aschepler Mar 24 '18 at 17:34
  • @aschepler Yeh, you are right these are overloads. Thanks! – Yola Mar 24 '18 at 17:38