Как я могу сделать шаблон функции друга шаблона класса видимым в области пространства имен? - PullRequest
0 голосов
/ 06 мая 2020

Я бы хотел скомпилировать приведенный ниже пример. У меня есть шаблон класса, который определяет шаблон функции друга, но по умолчанию он не отображается в окружающем пространстве имен.

namespace ns {
template<typename T> struct Foo {  // Class template.
    template<typename U> friend void bar(Foo, Foo<U> f) {  // Friend function template, defined inline.
        static_cast<void>(f.private_field);  // Should only compile when T=U.
    }
private:
    int private_field{};
};
}

int main() {
    bar(ns::Foo<char>{}, ns::Foo<char>{});      // Ok.
    ns::bar(ns::Foo<char>{}, ns::Foo<char>{});  // (1) FIXME: `ns::bar` not found.
    //bar(ns::Foo<bool>{}, ns::Foo<char>{});    // (2): Should fail because bar() here is not a friend of Foo<char>.
}

Я хотел бы иметь возможность вызывать ns::bar<int>(ns::Foo<char>{}) ((1) должен компилироваться) при этом не объявляя о дружбе между bar call и неродственным Foo<char> at (2). Как мне это сделать?

Если бы Foo не было шаблоном, я бы объявил template<typename U> void bar(Foo, Foo); в namespace ns.

Если бы bar не был шаблоном (например, с U=char fixed), я бы объявил его как функцию-шаблон вне класса и поддержал бы его полную специализацию, например здесь : friend void bar<T>(Foo, Foo<char> f);.

Однако оба являются шаблонами, и у меня нет идей.

UPD: Я пробовал использовать тот же трюк, что и с не-шаблоном bar, и сделать это шаблон вне класса. Однако похоже, что подружиться с частичной специализацией невозможно.

Моя попытка:

namespace ns {
template<typename T> struct Foo;

template<typename U, typename T> void bar(Foo<T>, Foo<U>);

template<typename T> struct Foo {
    template<typename U> friend void bar<U, T>(Foo, Foo<U> f);
    // template<typename U, typename TT> friend void bar(Foo<TT>, Foo<U> f);  // Compiles, but gives extra friendship to `bar`.
private:
    int private_field{};
};

template<typename U, typename T> void bar(Foo<T>, Foo<U> f) {
    static_cast<void>(f.private_field);  // Should only compile when T=U.
}
}

int main() {
    bar(ns::Foo<char>{}, ns::Foo<char>{});      // Ok.
    ns::bar(ns::Foo<char>{}, ns::Foo<char>{});  // (1) FIXME: `ns::bar` not found.
    //bar(ns::Foo<bool>{}, ns::Foo<char>{});    // (2): Should fail because bar() here is not a friend of Foo<char>.
}

Вывод G CC:

x.cpp:7:38: error: invalid use of template-id 'bar<U, T>' in declaration of primary template
    7 |     template<typename U> friend void bar<U, T>(Foo, Foo<U> f);
      |                                      ^~~~~~~~~
x.cpp: In instantiation of 'void ns::bar(ns::Foo<T>, ns::Foo<U>) [with U = char; T = char]':
x.cpp:19:41:   required from here
x.cpp:14:25: error: 'int ns::Foo<char>::private_field' is private within this context
   14 |     static_cast<void>(f.private_field);  // Should only compile when T=U.
      |                       ~~^~~~~~~~~~~~~
x.cpp:10:9: note: declared private here
   10 |     int private_field{};
      |         ^~~~~~~~~~~~~

1 Ответ

0 голосов
/ 06 мая 2020

Я думаю, что его нельзя спасти без функции моста из-за идентификаторов шаблонов и т. Д. c. В вашем первом подходе все ваши внутренние bar s:

для каждого T в Foo<T>:

template<typename U>
void bar(Foo<T>, Foo<U>);

, поэтому каждый Foo<T> создает собственную независимую перегрузку шаблона из bar. Это работает для ADL, но не работает для квалифицированного поиска, поскольку объявление друга не вводит его имя в пространство имен, см. N0777 от 1995 года. Это полностью обсуждается в вопросе .

Добавление одного общего объявления вроде этого:

template<typename T, typename U>
void bar(Foo<T>, Foo<U>);

не сработает, потому что этот идентификатор шаблона отличается от указанного выше. Он компилируется, но выдает ошибки связывания (потому что t<T, U> bar(Foo<T>, Foo<U>) нигде не определено).

У вашего второго подхода есть аналогичная проблема: ваш код пытался определить новую функцию из-за идентификатора шаблона и не может этого сделать (отсюда invalid use of template-id in declaration of primary template).

Ваша третья попытка фактически объявила специализацию друга bar (и у вас ровно 1 идентификатор шаблона), но, как вы утверждаете, она дает слишком много.

Если вы действительно хотите продолжить инкапсуляцию, я бы порекомендовал использовать функцию моста с отдельным идентификатором шаблона:

namespace ns {

    template<typename T> struct Foo {
        template<typename U> friend void bar_(Foo<T>, Foo<U> f) { // bar_ here
            static_cast<void>(f.private_field);
        }
    private:
        int private_field{};
    };

    template<typename T, typename U> void bar(Foo<T> x, Foo<U> f) { // end user gets a good name
        bar_(x, f); // ADL overload resolution, goes looking for friends and such.
    }

}

Также должно работать добавление некоторых аргументов диспетчеризации типов при сохранении имени bar.

...