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

Return type deduction for in-class friend functions

本文关键字:返回类型 函数 友元      更新时间:2023-10-16

这里有一个关于类内友元函数的返回类型推导的小实验(在这两种情况下都使用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中是否支持类内友元函数的自动返回类型推导?

关于其他答案:我们在这里明确处理n3638,以及它是如何被纳入最近的C++1y草案中的。

我使用的是commitee的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(); }

(但稍后会详细介绍)

这与任何尾随返回类型或依赖名称查找有根本不同,因为auto f();是一个初步声明,类似于struct A;。它需要稍后完成,然后才能使用(在需要返回类型之前)。

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


OP的示例是否合法,适用于n3638?

这是一个有点棘手的IMO.(我在这一节中总是提到9514cc28。)

1.哪里允许使用"auto">

[dcl.spec.auto]

6 nbsp;在本节未明确允许的上下文中使用autodecltype(auto)的程序是格式错误的。

2 占位符类型可以与函数声明符一起出现在decl说明符seq类型说明符seq转换函数id尾部返回类型中,在任何声明符有效的上下文中。

/5也定义了一些上下文,但它们在这里无关紧要。

因此,auto func()auto operator@(..)通常是允许的(这源于函数声明T D的组成,其中T的形式为decl说明符seq,而auto类型说明符)。


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.uqual]/9中的成员函数的名称查找/同一节的8指定了对成员函数体内部使用的名称的非限定查找。可以声明使用名称的方法之一是,它"应该是X类的成员或X(10.2)基类的成员">

struct X
{
void foo() { m = 42; }
int m;
};

注意mfoo中使用之前没有声明,但它是X的成员。

由此,我得出结论,即使

struct X
{
auto foo() { return m; }
int m;
}

是允许的。这是由clang++3.4中继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表达式L.b_引用当前实例化的成员(/4)。它也是当前实例化的一个依赖于的成员——这是在C++11之后添加的,请参阅DR1471,我不知道该怎么想:[temp.dep.expr]/5声明这个id表达式是依赖于而不是依赖于类型的,而且据我所见,[temp.dep.comxpr]并没有说它是依赖于值的。

如果L.b_中的名称不是依赖的,则名称查找将遵循"常规名称查找"规则。否则,这将很有趣(依赖名称查找没有很好地指定),但考虑到

template<class T>
struct A
{
int foo() { return m; }
int m;
};

也被大多数编译器所接受,我认为带有auto的版本也应该是有效的。

在[temp.friend]中也有一个关于模板之友的部分,但在IMO中,它没有说明这里的名称查找。


也可以在isocpp论坛上看到这一高度相关的讨论。