Участник шаблона класса без шаблона в качестве друга

Следующий фрагмент скомпилирован отлично с помощью gcc, icc и msvc (последний вопрос), но приключения совершаются с помощью <source>:6:9: error: calling a private constructor of class 'B<int>' в отмеченной строке. Тем не менее он отлично работает для бесплатной функции шаблона, как показано в коде:

struct A {
    template<class T>
    static void create () {
        T();
    }
};

template<class T>
void create() {
    T();
}

template<typename T>
struct B {

    friend void A::create<B>();
    friend void create<B>();

    private:
    B() = default;
};

int main() {
     A::create<B<int>>(); // clang trips here
     create<B<int>>(); // fine!
}

Какая может быть разница между статическим членом шаблона класса без шаблона и свободной функцией шаблона в этом контексте?

Ответы

Ответ 1

Я думаю, что ни один из этих вызовов не должен работать вообще, и что Кланг прав, по крайней мере, отвергая первый.

Стандарт C++ гласит:

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

  • если имя друга является квалифицированным или неквалифицированным идентификатором шаблона, декларация друга относится к специализации шаблона функции, в противном случае [...]

Имя вашей функции-друга void A::create, безусловно, является идентификатором шаблона, так как A::create - это шаблон. Таким образом, вы заявили, что экземпляр шаблона A::create<B> является другом (любым экземпляром) B

Это даже кажется законным, поскольку стандарт говорит:

Аргумент шаблона для параметра-шаблона, который является типом, должен быть идентификатором типа.

Проверяя грамматику, это даже кажется прекрасным:

type-id: type-specifier-seq [...]

Тип-спецификатор: простой тип-спецификатор [...]

type-specifier-seq: type-specifier [...]

простой тип-спецификатор:
inested-name-specifier opt
[...]
inested-name-specifier opt
Имя Шаблона

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

Clang и GCC, похоже, согласуются с этим, поскольку компилируются A::create<B>(); не создает какой-либо синтаксической ошибки или сообщения о неквалифицированном типе, но они говорят вам, что нет соответствующего шаблона. Clang:

error: no matching function for call to 'create'
     A::create<B>();

GCC:

error: no matching function for call to ‘A::create()
      A::create<B>();

Я не могу найти ничего в стандарте, который бы объяснил, почему friend void A::create<B>(); сделало бы все экземпляры A::create с инстанцированием B в качестве первого аргумента шаблона другом A. Следовательно, также последняя строка (create<B<int>>();) должна вызвать ошибку.


Ошибка, упомянутая в комментариях, не кажется связанной. Стандарт определяет, что

Шаблон друга может быть объявлен в шаблоне класса или класса. [...] В этих случаях все специализации шаблона класса друзей или друзей являются друзьями шаблона класса или класса, дающего дружбу.

Минимальный тестовый пример, представленный там, ясно показывает, что это поведение каким-то образом нарушается в Кланге.

Ответ 2

Я обнаружил ошибку, сообщенную для Clang: "Доступ к объявлению псевдонима общедоступного шаблона, который ссылается на закрытый вложенный тип друга, не разрешен", что похоже на эту проблему, поскольку это относится к доступу друга (шаблона в структуре, как в OP). частный член класса.

Неудачный тестовый пример:

struct FooAccessor
{
    template <typename T>
    using Foo = typename T::Foo;
};

class Type
{
    friend struct FooAccessor;        
    using Foo = int;
};

int main()
{
    FooAccessor::Foo<Type> t;
}

Результат:

$ clang++ test.cpp -std=c++11
test.cpp:4:5: error: 'Foo' is a private member of 'Type'
    using Foo = typename T::Foo;
    ^
test.cpp:11:11: note: implicitly declared private here
    using Foo = int;
          ^
1 error generated.