具有自动 return 类型推导的好友函数模板无法访问私有成员

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

抱歉这个问题的标题太复杂了;我试图描述我为这个问题构建的最小 SSCCE。

我有以下代码:

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

此代码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;
            ^

但是,如果您稍微更改代码(虽然对我来说不是很有用,因为在实际代码中这会引入大量样板文件):

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

突然works with that Clang 3.7.

不同的是,在不使用Clang编译的代码版本中,友元函数模板使用C++14 auto return类型推导,而工作函数模板显然说 returns intauto return 类型推导的其他变体也会出现同样的问题,例如 auto &&const auto &.

哪个编译器是正确的?请提供一些标准引号来支持答案,因为很可能需要为一个(...希望不是两个)编译器提交错误...或标准缺陷,如果两者都是正确的(这不会'不是第一次)。

看来您的第一个示例应该可行。 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>

该示例的原因似乎是为了解释要使声明匹配(并使定义的函数成为友元),结构 A 中的 frf 声明也需要使用 auto。这对我来说意味着允许使用 auto return 类型的友元声明,然后定义友元函数(以及使用 auto)。我找不到任何可以使成员函数模板的工作方式有所不同的东西,就像在您的示例中一样。

我认为这是一个 clang 错误。我想从这个方向接近它。与指定的 return 类型相比,auto 占位符类型增加了什么皱纹?来自 [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可以出现在foo的声明和定义中,并且有效。

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 ]

我们第一次需要使用确定表达式的类型时,函数的return类型已经从foo()定义中的return推导出来了, 所以这仍然有效。

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.

我们在这两个地方都使用了 auto,所以我们也没有违反这条规则。


简而言之,有几件事可以区分特定的 return 类型与占位符 return 类型与函数声明。但是示例中auto的所有用法都是正确的,因此命名空间范围foo应该被视为对class中最先声明的friend auto foo的重新声明和定义模板 bar。 clang 接受前者作为 return 类型 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 将把它放在最近的封闭命名空间中)接受相同的参数并具有相同的 return 类型(auto)?那应该是不可能的。