具有自动返回类型扣除的朋友功能模板无法访问私有成员

Gri*_*wes 25 c++ templates friend language-lawyer c++14

很抱歉这个问题的标题有多复杂; 我试图描述我为这个问题构建的最小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>{});
}
Run Code Online (Sandbox Code Playgroud)

此代码使用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;
            ^
Run Code Online (Sandbox Code Playgroud)

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

#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>{});
}
Run Code Online (Sandbox Code Playgroud)

它突然与Clang 3.7一起使用.

不同之处在于,在没有使用Clang编译的代码版本中,友元函数模板使用C++ 14 auto返回类型推导,而工作者明确表示它返回int.auto返回类型演绎的其他变体也会出现同样的问题,例如auto &&const auto &.

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

Bar*_*rry 6

我相信这是一个铿锵的错误.我想从这个方向接近它.auto与具有指定的返回类型相比,占位符类型添加了什么皱纹?来自[dcl.spec.auto]:

在这样的声明有效的任何上下文中,占位符类型可以在decl-specifier-seq,type-specifier-seq,conversion-function-idtrailing-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
}
Run Code Online (Sandbox Code Playgroud)

- 末端的例子]

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

具有使用占位符类型的声明返回类型的函数或函数模板的重新声明或特化也应使用该占位符,而不是推导类型.

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


简而言之,有几种方法可以将特定的返回类型与占位符返回类型与函数声明区分开来.但是auto示例中的所有用法都是正确的,因此命名空间范围foo应该被视为对friend auto foo类模板中第一个声明的重新声明和定义bar.clang接受前者作为返回类型的重新声明int但不适用的事实auto,并且没有相关的不同auto,这肯定表明这是一个错误.

此外,如果您删除int I模板参数以便可以调用foounqualified,则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)
         ^
Run Code Online (Sandbox Code Playgroud)

所以我们在同一个命名空间中有两个函数模板foo(因为从[namespace.memdef]将friend声明foo放在最近的封闭命名空间中),它们采用相同的参数并具有相同的返回类型(auto)?那是不可能的.