Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 264
Griwes Avatar asked Oct 01 '15 13:10

Griwes


People also ask

Can private members be accessed by friend functions?

A friend function cannot access the private and protected data members of the class directly. It needs to make use of a class object and then access the members using the dot operator. A friend function can be a global function or a member of another class.

Can friend function access private data of the class?

friend Function in C++A friend function can access the private and protected data of a class.


1 Answers

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.

like image 126
Barry Avatar answered Oct 25 '22 14:10

Barry