具有自动返回类型推断的朋友功能模板无法访问私有成员 [英] Friend function template with automatic return type deduction cannot access a private member

查看:74
本文介绍了具有自动返回类型推断的朋友功能模板无法访问私有成员的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

此代码使用GCC 5.2编译与该Clang 3.7一起使用.

区别在于,在未使用Clang编译的代码版本中,朋友功能模板使用C ++ 14 auto返回类型推导,而正在工作的人明确地说它返回了int. auto返回类型推导的其他变体,例如auto &&const auto &,也会发生相同的问题.

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

解决方案

我认为这是一个叮叮当当的错误.我想从这个方向着手.与具有指定的返回类型相比,auto占位符类型增加了哪些皱纹?来自[dcl.spec.auto]:

占位符类型可以与 decl-specifier-seq type-specifier-seq conversion-function-id 中的函数声明符一起出现. em>或 trailing-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
}

-结束示例]

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

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

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


简而言之,有几件事可以将特定的返回类型与占位符返回类型与函数声明区分开来.但是示例中auto的所有用法都是正确的,因此应将namespace-scope foo视为类模板bar中第一个声明的friend auto foo的重新声明和定义. clang接受前者作为返回类型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 函数模板(因为[c7>的friend声明将从[namespace.memdef]中将其放置在最近的封闭名称空间中)具有相同的参数并具有相同的返回类型(auto)?那不可能.

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

解决方案

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.

这篇关于具有自动返回类型推断的朋友功能模板无法访问私有成员的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆