Friend function template with automatic return type deduction cannot access a private member

。_饼干妹妹 提交于 2020-01-03 07:19:15

问题


Sorry for how complicated the title of this question is; I tried to describe the minimal SSCCE I constructed for this problem.

I have the following code:

#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>{});
}

This code compiles with GCC 5.2 and doesn't with 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;
            ^

However, if you change the code slightly (although in a way that is not exactly useful for me, since in the real code this would introduce tons of boilerplate):

#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>{});
}

it suddenly works with that Clang 3.7.

The difference is that in the version of the code that doesn't compile with Clang, the friend function template uses C++14 auto return type deduction, while the working one plainly says it returns int. The same problem also happens with other variants of auto return type deduction, like auto && or const auto &.

Which compiler is right? Please provide some standard quotes to support the answer, since it is quite possible that a bug will need to be filed for one (...hopefully not both) compilers... or a standard defect, if both are right (which wouldn't be the first time).


回答1:


I believe it's a clang bug. I want to approach it from this direction. What wrinkles does the auto placeholder type add, as compared to having a specified return type? From [dcl.spec.auto]:

The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (8.3.5), that trailing-return-type specifies the declared return type of the function. Otherwise, the function declarator shall declare a function. If the declared return type of the function contains a placeholder type, the return type of the function is deduced from return statements in the body of the function, if any.

auto can appear in foo's declaration and definition, and is valid.

If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression, the program is ill-formed. Once a return statement has been seen in a function, however, the return type deduced from that statement can be used in the rest of the function, including in other return statements. [ Example:

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 example ]

The first time we need to use determine the type of an expression, the return type of the function will already have been deduced from the return in the definition of foo(), so this is still valid.

Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type.

We're using auto in both places, so we don't violate this rule either.


In short, there are several things that differentiate a specific return type from an placeholder return type from a function declaration. But all the usages of auto in the example are correct, so the namespace-scope foo should be seen as a redeclaration and definition of the first-declared friend auto foo within class template bar. The fact that clang accepts the former as a redeclaration for return type int but not for auto, and there is no relevant different for auto, definitely suggests this is a bug.

Further, if you drop the int I template parameter so that you can call foo unqualified, clang will report the call as ambiguous:

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)
         ^

So we have two function templates named foo in the same namespace (since from [namespace.memdef] the friend declaration for foo will place it in the nearest enclosing namespace) that take the same arguments and have the same return type (auto)? That shouldn't be possible.




回答2:


It appears that your first example should work. There is a statement in C++14 (7.1.6.4 p12):

Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type. [ Example:

. . .

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

The reason for the example appears to be to explain that to make the declarations match (and cause the defined function to be a friend) the declaration of frf inside struct A would also need to use auto. This implies to me that having a friend declaration with an auto return type and later defining the friend function (and also using auto) is allowed. I can't find anything that would make this work differently for a member function template, like in your example.



来源:https://stackoverflow.com/questions/32889492/friend-function-template-with-automatic-return-type-deduction-cannot-access-a-pr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!