问题
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