Подтвердить что ты не робот

Ошибка clang? namespaced template class 'friend

Следующий код, который не компилируется под clang, но работает под gcc и VS:

template<typename T> class bar;

namespace NS
{
    template<typename T>
    class foo
    {
        foo() {}

        template<typename U> friend class bar;
    };
}

template<typename R>
class bar
{
public:
    bar()
    {
        NS::foo<int> f;
    }
};


int main(int, char **)
{
    bar<int> b;        
    return 0;
}

Сбой:

main.cpp:20:22: error: calling a private constructor of class 'NS::foo<int>'

        NS::foo<int> f;    
                     ^

main.cpp:8:9: note: implicitly declared private here

        foo() {}   
        ^

bar должен иметь доступ к foo частному конструктору, но похоже, что нет. Если я удалю namespace NS, он скомпилируется.

Код выглядит хорошо для меня, но, возможно, я не понимаю стандарт С++. Какой компилятор прав?

4b9b3361

Ответ 1

Я считаю, что clang является правильным. Согласно [namespace.memdef]/3:

Каждое имя, объявленное в пространстве имен, является членом этого пространства имен. Если объявление friend в нелокальный класс сначала объявляет класс, функцию, шаблон шаблона или шаблон функции. Друг является членом самого внутреннего охватывающего пространства имен.

В вашем случае имя не будет "объявлено первым" в объявлении friend. Позже в этом пункте, однако, внимание мое:

Если имя в объявлении friend не является ни квалифицированным, ни идентификатором шаблона и объявление - это функция или специфицированный тип-спецификатор, поиск для определения того, имеет ли сущность ранее объявленный , не должен рассматривать какие-либо области вне самого внутреннего охватывающего пространства имен.

То есть это объявление:

template<typename U> friend class bar;

не будет искать bar вне namespace NS, поэтому он не найдет ваше предыдущее объявление. Таким образом, он объявляет шаблон класса NS::bar<typename > равным friend foo. Вам нужно будет присвоить имя bar, чтобы оно было найдено:

template<typename U> friend class ::bar;

Это похоже на Ошибка GCC 37804.

Ответ 2

От cppreference:

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

void h(int);
namespace A {
  class X {
    friend void f(X); // A::f is a friend
    class Y {
        friend void g(); // A::g is a friend
        friend void h(int); // A::h is a friend, no conflict with ::h
    };
  };
  // A::f, A::g and A::h are not visible at namespace scope
  // even though they are members of the namespace A
  X x;
  void g() {  // definition of A::g
     f(x); // A::X::f is found through ADL
  }
  void f(X) {}       // definition of A::f
  void h(int) {}     // definition of A::h
  // A::f, A::g and A::h are now visible at namespace scope
  // and they are also friends of A::X and A::X::Y
}

Это не стандарт, но он в целом правильный. Таким образом, clang кажется правильным.

Ответ 3

Изменение кода на

template<typename T> class bar;

namespace NS
{
    template<typename T>
    class foo
    {
        foo() {}

        template<typename U> friend class ::bar;
    };
}

template<typename R>
class bar
{
public:
    bar()
    {
        NS::foo<int> f;
    }
};


int main(int, char **)
{
    bar<int> b;

    return 0;
}

компилируется с clang. Проблема заключается в поиске пространства имен. Код

template<typename U> friend class bar;

фактически объявил класс NS:: bar другом NS:: foo, поэтому foo не должен был быть затронут. Я предполагаю, что clang является стандартным, и код не должен компилироваться.