проблемы с разрешением функции друга шаблона - PullRequest
0 голосов
/ 12 октября 2018

У меня возникли проблемы с тем, чтобы заставить это работать.Вот MVCE моей проблемы, которая проходит фазу компиляции

template<typename T>
struct foo
{
    using type = T;
    friend type bar(foo const& x) { return x.X; }
    foo(type x) : X(x) {}
  private:
    type X;
};

template<typename> struct fun;
template<typename T> fun<T> bar(foo<T> const&, T);  // forward declaration

template<typename T>
struct fun
{
    using type = T;
    friend fun bar(foo<type> const& x, type y)
    { return {bar(x)+y}; }
  private:
    fun(type x) : X(x) {}
    type X;
};

int main()
{
    foo<int> x{42};
    fun<int> y = bar(x,7);    // called here
};

Требуется предварительное объявление, чтобы компилятор разрешил вызов в main() (см. этот ответ по причине).Однако теперь компилятор жалуется на этапе связывания / загрузки:

Неопределенные символы для архитектуры x86_64: "fun bar (foo const &, int)", на который ссылается: _main в foo-00bf19.o ld: символы не найдены для архитектуры x86_64

, хотя функция определена в объявлении друга.Если вместо этого я переместу определение вне определения struct func<>, т.е.

template<typename T>
struct fun
{
    using type = T;
    friend fun bar(foo<type> const& x, type y);
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

компиляция завершится неудачно с

foo.cc:29:10: error: calling a private constructor of class 'fun<int>'
{ return {bar(x)+y}; }

Итак, как я могу заставить это работать?(компилятор: Apple LLVM версии 9.0.0 (clang-900.0.39.2), c ++ 11)

Ответы [ 4 ]

0 голосов
/ 12 октября 2018

Есть некоторые изворотливые правила в отношении функций друзей.

namespace X {
  template<class T>
  struct A{
    friend void foo(A<T>) {}
  };
}

foo выше не является функцией шаблона.Это не шаблонная дружественная функция, которая существует в пространстве имен, включающем A, но ее можно найти только через поиск ADL;он не может быть непосредственно назван как X::foo.

Так же, как члены шаблонов могут быть самими шаблонами, так и нет, это не-шаблонная функция друга, которая создается для каждого экземпляра класса шаблона A.

namespace X{
  template<class T>
  void foo(A<T>);
}

Этот foo является шаблоном функции с именем foo в пространстве имен X.Это не то же самое , что и у функции друга без шаблона foo выше.

Из этого большинства ваших ошибок ясно.То, что вы считали предварительным объявлением, было несвязанной функцией шаблона.То, что вы считали другом, не было, поэтому у вас не было доступа к приватному конструктору.

Мы можем исправить это несколькими способами.Мой любимый способ - добавить тип тега.

template<class T>struct tag_t{using type=T;};
template<class T>
constexpr tag_t<T> tag{};

теперь мы можем использовать tag для отправки ADL:

template<typename T>
struct fun {
  using type = T;
  friend fun bar(tag_t<fun>, foo<type> const& x, type y)
  { return {bar(x)+y}; }
private:
  fun(type x) : X(x) {}
  type X;
};

, а затем в основном это работает:

foo<int> x{42};
fun<int> y = bar(tag<fun<int>>, x, 7);    // called here

Но вы, возможно, не захотите упоминать tag<fun<int>>, поэтому мы просто создаем не-друга bar, который звонит нам:

template<class T>
fun<T> bar(foo<T> const& x, type y)
{ return bar( tag<T>, x, y ); }

, и теперь ADL делает свою магию инайден правильный не шаблон bar.

Другой подход заключается в том, чтобы сделать функцию template bar другом fun.

0 голосов
/ 12 октября 2018

Этот компилируется для меня:

template<typename T>
struct foo
{
    using type = T;
    friend type bar(foo const& x) { return x.X; }
    foo(type x) : X(x) {}
  private:
    type X;
};

template<typename> struct fun;
template<typename T> fun<T> bar(foo<T> const&, T);  // forward declaration

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<type>(foo<type> const& x, type y);
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

int main()
{
    foo<int> x{42};
    fun<int> y = bar(x,7);    // called here
};

Использование GCC 8.2.1.Что я добавил, так это обозначение шаблона в объявлении друга.

0 голосов
/ 12 октября 2018

Объявление друга внутри fun должно соответствовать прямому объявлению шаблона функции, в противном случае оно вызовет несвязанную функцию:

template<typename TT> fun<TT> friend ::bar(foo<TT> const &, TT);

Хотя определение должно быть размещено за пределами:

template<typename T> fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

онлайн-компилятор

Короче код, демонстрирующий проблему:

void foo(void);

template<typename T>
struct bar
{
    friend void foo(void) {}
};

int main()
{
    foo(); // undefined reference to `foo()'
    return 0;
}

Определение функции в классе никогда не будет использоваться:

17.8.1 Неявная реализация [temp.inst]

Неявная реализация специализации шаблона класса вызывает неявную реализацию объявлений, но не определений, аргументов по умолчанию или спецификаторов noexcept для функций-членов класса, классов-членов, перечислений членов-областей, статических членов-данных, членашаблоны и friends;
0 голосов
/ 12 октября 2018

Хорошо, я нашел ответ:

Чтобы объявление друга работало правильно, оно должно быть квалифицировано как функция template , т.е.

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<T>(foo<type> const& x, type y);
    //            ^^^
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

Однако объединить определение с объявлением друга все равно не удается:

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<T>(foo<type> const& x, type y)
    { return {bar(x)+y}; }
  private:
    fun(type x) : X(x) {}
    type X;
};

приводит к:

foo.cc:20:16: warning: inline function 'bar<int>' is not defined [-Wundefined-inline]
    friend fun bar<T>(foo<T> const& x, T y)
               ^
1 warning generated.
Undefined symbols for architecture x86_64:
  "fun<int> bar<int>(foo<int> const&, int)", referenced from:
      _main in foo-c4f1dd.o
ld: symbol(s) not found for architecture x86_64

, который я не совсем понимаю, так как предварительное объявление все еще действует.

...