【问题标题】:Friend function template with automatic return type deduction cannot access a private member带自动返回类型扣除的好友函数模板无法访问私有成员
【发布时间】:2015-12-29 14:53:37
【问题描述】:

对不起,这个问题的标题太复杂了;我试图描述我为这个问题构建的最小 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.2doesn'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 返回类型推导,而工作的则明确表示返回 intauto 返回类型推导的其他变体也会出现同样的问题,例如 auto &amp;&amp;const auto &amp;

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

【问题讨论】:

  • 我很惊讶。我认为类中的朋友函数与外部函数无关,而无需在朋友之前声明类之外的函数。然而,情况似乎并非如此。唔。在实践中,-&gt;decltype 是否可行?
  • @Yakk A friend 声明可以在最里面的封闭命名空间中声明一个函数。我不认为auto 在这里有什么特别之处,并怀疑这是一个clang 错误,但无法想出一套好的措辞来支持这一点。
  • 如果你删除了 int I 模板参数,并对 foo() 进行了不合格的调用,clang 会将调用报告为模棱两可......所以出于某种原因它确实认为有两个单独的函数模板foos.
  • 我认为它引入了它,但出于某种原因认为它会有所不同,除非首先引入“外部”。我不知道为什么我会这么想。
  • 啊哈!因为内联定义友元确实遵循该规则: [class.friend]/7 -- 非内联友元函数位于封闭命名空间的词法范围内。 (这就是我产生这种误解的原因)

标签: c++ templates language-lawyer c++14 friend


【解决方案1】:

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

占位符类型可以在decl-specifier-seqtype-specifier-seqconversion-function-id中与函数声明符一起出现em> 或 trailing-return-type,在此类声明符有效的任何上下文中。如果函数声明器包含 trailing-return-type (8.3.5),则 trailing-return-type 指定声明的返回类型 的功能。否则,函数声明器应声明一个函数。如果声明的返回类型为 该函数包含一个占位符类型,该函数的返回类型是从返回语句推导出来的 在函数体中,如果有的话。

auto可以出现在foo的声明和定义中,并且有效。

如果需要使用未推导占位符类型的实体类型来确定表达式的类型, 该程序格式不正确。但是,一旦在函数中看到 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的所有用法都是正确的,所以命名空间范围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 的函数模板(因为从 [namespace.memdef] 中,foofriend 声明会将其放置在最近的封闭命名空间中)采用相同的参数并具有相同的返回类型 (auto)?那应该是不可能的。

【讨论】:

  • 肯定有一个clang错误:coliru.stacked-crooked.com/a/90ad356e8a27fbbd它似乎通过friend-declaration声明了另一个函数,即使该声明使用了qualified-id。
  • 哦——那么实际的错误实际上会被引入以重现原始问题的附加模板隐藏?那很有意思。但这也是 - 我认为 - 很好,因为这是一个可能更容易被 Clang 开发人员跟踪的错误。
  • @Griwes 我不认为它是隐藏的,只是另一个例子。在您原来的情况下,clang 仍然会生成两个单独的 foos。 friended 不是可调用限定的,因此它明确调用非friended 。
  • @Barry:是的,但无法访问该成员的确切原因在原始 SSCCE 中并不完全可见;您只能说该函数无法访问该成员,但不清楚编译器实际上正在创建两个函数。
  • 为什么删除第一个模板参数会改变错误?
【解决方案2】:

看来您的第一个示例应该可以工作。 C++14(7.1.6.4 p12)中有这样的说法:

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

。 . .

template <typename T> struct A {
    friend T frf(T);
};
auto frf(int i) { return i; } // not a friend of A<int>

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

【讨论】:

    猜你喜欢
    • 2014-08-21
    • 1970-01-01
    • 2021-08-16
    • 2021-08-05
    • 2015-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多