当前位置: 首页 > 知识库问答 >
问题:

友元函数模板在类模板中返回导出依赖类型的行为

岳泉
2023-03-14

我遇到了以下代码,其行为是由所有GCC、Clang和MSVC商定的:

#include <concepts>

template<typename T>
auto foo(T);

template<typename U>
struct S {
    template<typename T>
    friend auto foo(T) {
        return U{};
    }
};

S<double> s;
static_assert(std::same_as<decltype(foo(42)), double>);

现场演示:https://godbolt.org/z/hK6xhesKM

foo()在全局命名空间中声明,并带有推断的返回类型<代码>S

我所期望的是,当用〈code〉U=double〈code〉实例化〈code〉S〈code〉时,它对〈code〉foo()〈code〉的定义被放入全局命名空间,并且〈code〉U〈code〉被替换,因为友元函数是如何工作的,有效地如下所示:

template<typename T>
auto foo(T);

S<double> s;

template<typename T>
auto foo(T) {
    return double{};
}

因此,我希望foo()的返回类型是double,并且应该传递以下静态断言:

static_assert(std::same_as<decltype(foo(42)), double>);

然而,实际情况是,这三个编译器在行为上存在分歧。

html" target="_blank">GCC像我预期的那样传递静态断言。

Clang使静态断言失败,因为它似乎认为foo()返回int:

'标准::is\u same\u v

MSVC产生了完全不同的错误:

'U':未声明的标识符

我觉得奇怪的是,对于一个看似简单的代码示例,这里所有的编译器都有不同的行为
如果未模板化(foo()),或者没有推导出返回类型(请参阅此处演示),则不会出现问题。

哪个编译器的行为正确?或者,代码是格式错误的NDR还是未定义的行为?(为什么?)

共有1个答案

柯清野
2023-03-14

哪个编译器的行为正确?或者,代码是格式错误的NDR还是未定义的行为?(为什么?)

正如@Sedenion在一篇评论中指出的那样,在谈到CWG 2118的领域时(我们将进一步讨论这个问题),该程序是按照当前标准编写的,GCC接受它是正确的,而根据[dcl.spec.auto.general]/12和/13[emphasis mine]的规定,Clang和MSVC拒绝它是错误的:

/12当定义被实例化时,即使函数体包含具有非类型依赖操作数的返回语句,作为函数或在其声明类型中具有占位符的函数模板的模板实体也会发生返回类型推断。

/13使用占位符类型的声明返回类型的函数或函数模板的重新声明或专门化也应使用该占位符,而不是推导类型。同样,不使用占位符类型的声明返回类型的函数或函数模板的重新声明或专门化不应使用占位符。

[示例6:

auto f();
auto f() { return 42; }            // return type is int
auto f();                          // OK
// ...
template <typename T> struct A {
  friend T frf(T);
};
auto frf(int i) { return i; }      // not a friend of A<int>

尤其是“不是A的朋友”

/12,连同[temp.inst]/3(下文)一起,涵盖了只有在从s的实例化中提供了朋友的主模板定义(正式:它的声明而不是定义已实例化)之后,才发生朋友的返回类型推断

类模板专门化的隐式实例化导致

  • (3.1)未删除类成员函数、成员类、作用域成员枚举、静态数据成员、成员模板和友元的声明(而非定义)的隐式实例化;和
  • […]

然而,为了根据[basic.def.odr]和[class.mem]确定实例化的重新声明是否有效,与模板中的定义相对应的声明被视为定义。

[示例4:

// ...

template<typename T> struct Friendly {
  template<typename U> friend int f(U) { return sizeof(T); }
};
Friendly<char> fc;
Friendly<float> ff;  // error: produces second definition of f(U)

-结束示例]

正如在《愚蠢的树叶》中详细介绍的那样,可以依靠类模板的单个第一次实例化来控制朋友的定义。首先,这里的意思是基本上依赖元编程状态来指定此定义。

在OP的示例中,第一个实例化是

 类似资料:
  • 我想知道如果函数的模板参数包括但不限于类的模板参数,如何使函数成为类的朋友并在类外定义函数。 例如,我有以下模板类和模板朋友函数: 如果我编译: 我会得到以下链接器错误:

  • 我有一个类模板和一个函数模板定义了一个,它引用要绑定到的模板类型。 我想要的是将< code>make_obj函数声明为< code>friend,这样它可以创建< code>Obj的,但是其他人不能(除了通过copy ctor)。 我尝试了几个朋友声明,包括 和 后者是使< code>make_obj的所有模板实例化成为< code>Obj类的朋友的不太理想的尝试。然而,在这两种情况下,我得到相

  • 我试图使乘法运算符成为名为TVector3的模板类的朋友。我读过,我可以在类声明中声明朋友函数之前,对其进行前向声明,但我这样做的尝试是徒劳的。我知道我可以简单地定义friend函数而不是声明它,但我希望它能与前向声明技术一起工作。 特别是,我试图为我的案例实施这个解决方案。我发现这篇文章也是David Rodriguez给出的解决方案(第三个版本),但我不知道我做错了什么。 我使用'g temp

  • 我正在学习一个视频教程,我想声明一个模板函数作为模板类的朋友。我不知道为什么代码会抛出错误。 编译器抛出错误。 错误: templates\u friends\u 38。cpp:在“void doSomething2(T)[T=int]”的实例化中:templates\u friends\u 38。cpp:40:19:此处需要templates\u friends\u 38。cpp:32:9:错误

  • 考虑以下示例: 使用GCC 5.2编译会引发以下编译错误: 但是标准在14.6.5中说: 友元类或函数可以在类模板中声明。当模板被实例化时,其朋友的名称被视为在实例化点显式声明了专门化。 为什么编译失败?在GCC 3.4中,通过。