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

Шаблон функции Friend с автоматическим выводом типа возврата не может получить доступ к частному члену

Извините за сложность названия этого вопроса; Я попытался описать минимальный SSCCE I, построенный для этой проблемы.

У меня есть следующий код:

#include <iostream>

namespace fizz
{
    template<typename... Ts>
    class bar
    {
    public:
        template<int I, typename... Us>
        friend auto foo(const bar<Us...> &);

    private:
        int i = 123;
    };

    template<int I, typename... Ts>
    auto foo(const bar<Ts...> & b)
    {
        return b.i;
    }
}

int main()
{
    std::cout << fizz::foo<1>(fizz::bar<int, float>{});
}

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

main.cpp:19:18: error: 'i' is a private member of 'fizz::bar<int, float>'
        return b.i;
                 ^
main.cpp:25:24: note: in instantiation of function template specialization 'fizz::foo<1, int, float>' requested here
    std::cout << fizz::foo<1>(fizz::bar<int, float>{});
                       ^
main.cpp:13:13: note: declared private here
        int i = 123;
            ^

Однако, если вы немного измените код (хотя это не совсем полезно для меня, поскольку в реальном коде это приведет к появлению тонны шаблона):

#include <iostream>

namespace fizz
{
    template<typename... Ts>
    class bar
    {
    public:
        template<int I, typename... Us>
        friend int foo(const bar<Us...> &);

    private:
        int i = 123;
    };

    template<int I, typename... Ts>
    int foo(const bar<Ts...> & b)
    {
        return b.i;
    }
}

int main()
{
    std::cout << fizz::foo<1>(fizz::bar<int, float>{});
}

он внезапно работает с этим Clang 3.7.

Разница в том, что в версии кода, который не компилируется с помощью Clang, шаблон функции friend использует С++ 14 auto return type deduction, в то время как рабочий говорит, что возвращает int. Такая же проблема возникает и с другими вариантами вывода типа auto типа типа auto && или const auto &.

Какой компилятор прав? Пожалуйста, предоставьте некоторые стандартные кавычки для поддержки ответа, так как вполне возможно, что ошибка должна быть подана для одного (... надеюсь, не обоих) компиляторов... или стандартного дефекта, если оба они правы (что бы не было " t в первый раз).

4b9b3361

Ответ 1

Я считаю, что это ошибка. Я хочу приблизиться к нему с этого направления. Какие морщины добавляют тип заполнителя auto, по сравнению с указанным типом возврата? Из [dcl.spec.auto]:

Тип заполнителя может появляться с объявлением функции в описании-specifier-seq, type-specifier-seq, function-function-id или trailing-return-type в любом контексте, где такой декларатор действителен. Если декларатор функции включает в себя тип trailing-return-type (8.3.5), то trailing-return-type указывает объявленный тип возврата функции. В противном случае декларатор функции должен объявить функцию. Если объявленный тип возврата функция содержит тип заполнителя, возвращаемый тип функции выводится из операторов return в теле функции, если таковая имеется.

auto может отображаться в foo декларации и определении и действителен.

Если тип объекта с неопределенным типом заполнителя необходим для определения типа выражения, программа плохо сформирована. Однако, как только оператор return был замечен в функции, возвращаемый тип выведенный из этого утверждения, может использоваться в остальной части функции, в том числе в других операторах return. [Пример:

auto n = n;              // error, n’s type is unknown
auto f();
void g() { &f; }         // error, f’s return type is unknown
auto sum(int i) {
  if (i == 1)
    return i;            // sum’s return type is int
  else
    return sum(i-1)+i;   // OK, sum’s return type has been deduced
}

-end пример]

В первый раз, когда нам нужно определить тип выражения, возвращаемый тип функции уже будет выведен из return в определении foo(), поэтому это все еще актуально.

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

Мы используем auto в обоих местах, поэтому мы не нарушаем это правило.


Короче говоря, существует несколько вещей, которые различают конкретный тип возвращаемого значения из возвращаемого типа заполнителя из объявления функции. Но все примеры использования auto в этом примере верны, поэтому область пространства имен foo должна рассматриваться как переопределение и определение первого объявленного friend auto foo внутри шаблона класса bar. Тот факт, что кланг принимает первое слово как receclaration для типа возврата int, но не для auto, и для auto нет никакого релевантного различия, определенно предполагает, что это ошибка.

Кроме того, если вы отбросите параметр шаблона int I, чтобы вы могли называть foo неквалифицированным, clang сообщит о вызове как неоднозначное:

std::cout << foo(fizz::bar<int, float>{});

main.cpp:26:18: error: call to 'foo' is ambiguous
    std::cout << foo(fizz::bar<int, float>{});
                 ^~~
main.cpp:10:21: note: candidate function [with Us = <int, float>]
        friend auto foo(const bar<Us...> &);
                    ^
main.cpp:17:10: note: candidate function [with Ts = <int, float>]
    auto foo(const bar<Ts...>& b)
         ^

Итак, у нас есть два шаблона функций с именем foo в том же пространстве имен (поскольку из [namespace.memdef] объявление friend для foo поместит его в ближайшее окружение), которое принимает те же аргументы и имеет тот же тип возврата (auto)? Это не должно быть возможным.

Ответ 2

Кажется, что ваш первый пример должен работать. В С++ 14 есть оператор (7.1.6.4 p12):

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

. , .

template <typename T> struct A {
    friend T frf(T);
};
auto frf(int i) { return i; } // not a friend of A<int>

Причиной для примера является объяснение того, что для того, чтобы объявления были сопоставлены (и заставить определенную функцию быть другом), объявление frf внутри struct A также должно было бы использовать auto. Это подразумевает, что допускается объявление объявления с типом автоматического возврата и последующим определением функции друга (а также с использованием авто). Я не могу найти ничего, что могло бы сделать эту работу по-разному для шаблона функции-члена, как в вашем примере.