类内友元函数的返回类型推导

30

这是一个使用Clang 3.4 SVN和g++ 4.8.1(在两种情况下都使用了std=c++1y)的小实验,涉及到内部友元函数的返回类型推断,这在链接的工作文件中没有记录。

#include <iostream>

struct A
{
    int a_;
    friend auto operator==(A const& L, A const& R) 
    { 
        return L.a_ == R.a_; // a_ is of type int, so should return bool
    }
};

template<class T>
struct B
{
    int b_;
    friend auto operator==(B const& L, B const& R) 
    { 
        return L.b_ == R.b_; // b_ is of type int, so should return bool
    }
};

using BI = B<int>;

int main()
{
    std::cout << (A{1} == A{2}) << "\n";    // OK for Clang, ERROR for g++
    std::cout << (BI{1} == BI{2}) << "\n";  // ERROR for both Clang and g++
}

实时示例.

问题: C++14 是否支持类内友元函数的自动返回类型推断?


1
我认为问题并不特别在于模板内定义的友元函数实时例子有一条规则是:“当实例化定义时,带有占位符的函数模板的返回类型推导会发生,即使函数体包含具有非类型相关操作数的返回语句”,但我找不到任何关于类模板成员或类模板内定义的友元函数的信息。 - dyp
1
由于[dcl.spec.auto]/12中有“当函数在类模板的友元函数声明中定义时,该函数在odr-used时实例化。”,因此[dcl.spec.auto]/12中的措辞可能应为“函数模板、类模板成员函数和在类模板内定义的友元函数的返回类型推导”。 - dyp
1
该委员会的 Github 代码库(https://github.com/cplusplus/draft)已经包含了那份文件。 - dyp
@JonathanWakely 如果在main函数中第二个比较被注释掉,clang++ 3.4 (trunk 184460)可以编译这个例子。这可能追溯到类模板中定义的友元函数只有在odr-used时才会实例化(就我所看到的而言,标准中没有定义实例化的时机)。 - dyp
我总是不愿意编辑标签,尤其是如果比我声望更高的用户设置它们;) 但在C++11中,唯一可能的返回类型推断是针对lambda表达式和尾随返回类型..(好吧,我想写点什么,但你已经改变了它;) - dyp
显示剩余17条评论
1个回答

11
关于其他回答:我们在这里明确地处理的是n3638以及它如何被纳入最近的C++1y草案中。
我正在使用来自委员会的Github存储库的9514cc28,它已经包含了一些(轻微的)对n3638的修复/更改。
n3638允许明确地:
struct A {
  auto f(); // forward declaration
};
auto A::f() { return 42; }

而且,正如我们可以从[dcl.spec.auto]推断出的那样,在此功能被指定的地方,甚至以下内容也是合法的:
struct A {
  auto f(); // forward declaration
};

A x;

auto A::f() { return 42; }

int main() { x.f(); }

(但稍后会更详细地讨论)这与任何尾随返回类型或相关名称查找 fundamentally 不同,因为auto f();是一个初步声明,类似于struct A;。它需要在使用之前(在需要返回类型之前)完成。

此外,OP中的问题与内部编译器错误有关。最近的clang++3.4 trunk 192325 Debug + Asserts版本无法编译,因为在解析行return L.b_ == R.b_;时出现了断言失败。截至目前,我还没有检查过g ++的最新版本。


OP的例子在n3638方面是否合法?
我认为这有点棘手。(在这一节中,我总是指9514cc28。)
1.允许在哪里使用“auto”?

[dcl.spec.auto]

“使用autodecltype(auto)在本节中未明确允许的情况下,程序是不合法的。占位符类型可以与函数声明符一起出现在decl-specifier-seqtype-specifier-seqconversion-function-idtrailing-return-type中,在任何这样的声明符有效的上下文中。因此,auto func()auto operator@(..)通常是允许的(这是由函数声明的组成形式T D决定的,其中Tdecl-specifier-seq的形式,而auto是一个type-specifier)。”

2. 是否允许编写“auto func();”,即一个声明而非定义的函数声明?

[dcl.spec.auto]/1 规定:

autodecltype(auto)类型说明符,它们指定了一个占位符类型,该类型将在初始化器推导或使用尾置返回类型进行显式指定时被替换。

并且 /2 规定:

如果函数的声明返回类型包含一个占位符类型,则函数的返回类型将从函数体中的return语句(如果有)中推导出来。

虽然它没有明确地允许像auto f();这样的函数声明(即声明而不是定义),但是根据 n3638 和 [dcl.spec.auto]/11 的规定,它被认为是被允许的,而且没有明确禁止。


3. 朋友函数怎么样?

到目前为止,这个例子

struct A
{
    int a_;
    friend auto operator==(A const& L, A const& R);
}

auto operator==(A const& L, A const& R)
{ return L.a_ == R.a_; }

应该是格式良好的。现在有趣的部分是在 A 的定义内部定义友元函数的定义,即
struct A
{
    int a_;
    friend auto operator==(A const& L, A const& R)
    { return L.a_ == R.a_; } // allowed?
}

在我看来,这是允许的。为了支持这一点,我会引用名称查找。在友元函数声明中定义的函数的定义内部进行名称查找遵循成员函数的名称查找,如[basic.lookup.unqual]/9所述。在同一节的/8中,指定了名称在成员函数体内使用的无资格名称查找。一个名称被声明为被使用的方式之一是它“必须是类X的成员或者是X的一个基类的成员(10.2)”。这允许使用广泛知道的方法。
struct X
{
    void foo() { m = 42; }
    int m;
};

注意在foo中使用m之前没有声明它,但它是X的一个成员。
由此可以得出结论,即即使
struct X
{
    auto foo() { return m; }
    int m;
}

允许这样做。这得到了clang++3.4 trunk 192325的支持。名称查找要求在struct完成之后才能解释此函数,还要考虑:
struct X
{
    auto foo() { return X(); }
    X() = delete;
};

同样地,定义在类内部的友元函数的主体只有在类完成后才能被解释。

4. 模板怎么办?

具体来说,friend auto some_function(B const& L) { return L.b_; } 怎么处理?

首先,“注入类名” B 相当于 B<T>,参见 [temp.local]/1。它指代“当前实例化”([temp.dep.type]/1)。

“id-expression” L.b_ 指代“当前实例化”的一个成员(/4)。它也是“当前实例化”的一个“相关成员”,这是 C++11 之后增加的内容,参见 DR1471,我不知道该怎么看待它:[temp.dep.expr]/5 表示这个“id-expression”不依赖于类型,就我所知 [temp.dep.constexpr] 也没有表明它依赖于值。

如果L.b_中的名称不是依赖项,则名称查找将遵循[ temp.nondep ]的“通常名称查找”规则。否则,这将很有趣(依赖项名称查找未经过很好的指定),但考虑到…
template<class T>
struct A
{
    int foo() { return m; }
    int m;
};

大多数编译器也接受带有 "auto" 的版本,我认为这个版本也应该是有效的。在 [temp.friend] 中还有一个关于模板友元的部分,但我认为它对此处的名称查找没有什么帮助。
另请参见isocpp-forum中这个高度相关的讨论

如果可以的话,我愿意给予+10分的评价,但这是非常棒的内容,需要我去处理。非常感谢! - TemplateRex
如果我没记错的话,类模板中定义的友元函数是封闭命名空间中的常规函数,只能通过ADL找到。既然您提到了与实例化相关的某些技术细节,我想知道这里是否会有2阶段名称查找的干扰。具体来说,返回类型何时被推断? - TemplateRex
@TemplateRex [class.friend]/7 "在一个类中定义的friend函数处于其定义所在类的(词法)作用域中。" - 这会影响未限定查找(还隐式具有inline属性)。 "特别地,返回类型什么时候被推导出来?" 很好的问题;我已经在你的问题评论中说过我找不到明确的说明。然而,正如链接的isocpp讨论中也出现的那样,它不能在封闭类完成之前确定。 - dyp
好的,我想需要考虑标准和当前的质量要求,也许在未来几个月中会更清楚。如果有更好的答案出现,我将在周末结束时接受它。 - TemplateRex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接