Объявите шаблонную функцию, берущую два объекта класса шаблона friend для (только) этих двух специализаций


У меня есть класс шаблона Test (с целым числом в качестве аргумента шаблона) и функция шаблона (в данном случае operator*), которая принимает два объекта класса Test С возможно различными аргументами шаблона. Функция должна быть дружественной к обоим своим аргументам. Вот минимально рабочий пример:

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

Это работает, но функция является другом для всех специализаций Test. Я хочу, чтобы он был другом только с Test<N1> и Test<N2>.

Я попытался объявить это так: это:

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

Но встречались ошибки g++ для неоднозначных перегрузок. Я также попытался:

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

Но аргументы шаблона по умолчанию не допускаются для объявлений друзей.

Я предпочитаю решение в C++14, но решения в C++17 также были бы приемлемы.

Обновление: после ответа S. M. Я предлагаю следующее решение для тех, у кого есть подобные проблемы

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

2 ответа:

Я предлагаю решение ниже. Эта функция больше не является функцией друга.

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

Как член 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;
}

Нет, ты не можешь этого сделать.

17.5.4 друзья [темп.друг]

Другом класса или шаблона класса может быть шаблон функции или шаблон класса, специализация шаблона функции или шаблона класса, или не-шаблон функции или класса.

Из этих 6 вариантов вам может быть интересен шаблон функции.

Таким образом, вы можете подружиться с оператором тремя способами
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

Вариант №1 Вы пробовали, и он не работает, как вы хотелось бы этого. Варианты #2 и #3 также не будут работать по двум причинам: Во-первых, вам нужно будет где-то определить эту перегрузку, во-вторых, если вы определяете такую перегрузку, она не будет работать со вторым параметром (#2) и с первым параметром (#3).