C++23’s New Feature: Deducing this

C++23’s New Feature: Deducing this

0. 引言

C++23带来了新的语法支持:Deducing this,作为C++23中的重要特性,使C++程序的编写更加简洁和灵活,但也带入了一定的理解成本。本篇文章旨在介绍该特性的语法、基本使用与一些使用场景。

本文中所有示例代码的编译及运行环境:Windows, clang+llvm-18.1.8-pc-x86_64-windows-msvc

编译器及参数:clang++ -std=c++23

1. 简单了解Deducing this

1.1 Deducing this解决了什么问题?

P0847R7.Motivation

In C++03, member functions could have cv-qualifications, so it was possible to have scenarios where a particular class would want both a const and non-const overload of a particular member. (Note that it was also possible to want volatile overloads, but those are less common and thus are not examined here.) In these cases, both overloads do the same thing — the only difference is in the types being accessed and used. This was handled by either duplicating the function while adjusting types and qualifications as necessary, or having one overload delegate to the other.

In C++11, member functions acquired a new axis to specialize on: ref-qualifiers. Now, instead of potentially needing two overloads of a single member function, we might need four: &, const&, &&, or const&&

P0847R7.Proposal

We believe that the ability to write cv-ref qualifier-aware member function templates without duplication will improve code maintainability, decrease the likelihood of bugs, and make fast, correct code easier to write.

我们可以看出,在(C++23)之前,很多时候为了应对不同cv-ref qualified的对象,我们需要对类成员函数做出&, const&, &&const&&的重载,然而,这些所有的重载函数都在执行同一份任务,毫无疑问,这是一种形式的冗余。而Deducing this正是为解决这一问题而生。

1.2 Deducing this的如何解决上述问题?

在C++23之前,一个类成员函数的代码可能如下:

class A {
public:
    void foo() & {
        // do something
    }

    void foo() const& {
        // do the same thing
    }

    void foo() && {
        // do the same thing
    }

    void foo() const&& {
        // do the same thing
    }
};

在C++23之后,它们可以被统一为一个函数:

class A {
public:
    template<typename Self>
    void foo(this Self&& self) {
        // do something
    }
};

这便是Deducing this的基本写法,self称为显式对象形参,其语法为:

ret-type member-function-name(this type-name param-name)

它说明了我们将以何种类型来操作对象,且在名字查找中,void A::foo() const&void A::foo(this A const& self)等价,并以此类推。

同样地,正是因为当前对象作为形参,使得当前的对象类型可以利用模板(this auto&&也可行)进行类型推导,也就实现了Deducing this,统一了四种函数。

注意:显示对象形参只能作为非虚(见下文2.6说明)的,非静态的成员函数的首个形参。

void foo(this auto& self) {} // correct
void foo(int a, this auto& self) {} // error
static void foo(this auto& self) {} // error
virtual void foo(this auto& self) {} // error

2. 关于Deducing this的语法细节

2.1 如果要对不同的成员函数做出差异化处理怎么办?

有两种方法,第一种是进行函数重载,使用cv-ref修饰函数或使用cv-ref修饰显式对象形参类型;第二种是写出函数模板特化。

注意:void A::foo(this A&)在语义上类似static函数,无法与static void A::foo()进行重载(注:

A member function with an explicit object parameter is a third kind of member function that’s sort of halfway in between those two(all member functions are either static member functions or non-static member functions). They’re like semi-static member functions.

P0847R7.Not quite static, not quite non-static)。

另外,(重载决议的规则)如果:

  • 显式对象形参声明为this auto类型,那么当成员函数中出现其它显式对象形参类型的重载时:
    • 如果重载类型为&&类型,被右值调用时,优先考虑具有this auto类型的显显式对象形参的函数;
    • 如果重载类型为&类型,被左值调用时,优先考虑该重载。
  • 显式对象形参声明为模板的非引用类型,而其它重载函数是该模板的特化时:
    • 如果模板特化为&&&类型,被相应引用类型调用时,优先考虑非模板特化函数。
  • 如果存在按值类型传递对象的重载,始终优先考虑这一重载,且同时如果:
    • 定义了this <class-type> cv-qulifier&或者this <class-type> cv-qulifier&&的重载函数,编译可能通过,但一定会产生模糊调用。

(以此类推,等待更新)

所以,在编写具有显式对象形参的成员函数时,需要注意所编写的成员函数重载在调用时是否与所期待的语义相同。

2.2 显式对象形参可以拥有非完整类型吗?

可以,但是当使用类型时必须完整。

P0847R7.Pathological cases

Following the precedent of [P0929R2], we think this should be fine, albeit strange. If D is incomplete, we simply postpone checking until the point where we actually need a complete type, as usual. At that point D().foo() would be a valid expression. We see no reason to reject.

例如:

struct D;
struct B {
    void foo(this D const&)
    { std::cout << "D" << std::endl; }
}
struct D : B {};

可以成功通过编译。

2.3 显式对象形参可以不具有本类类型吗?

可以,但是无法以常规方式调用。

例如void B::foo(this int i),无法通过1.foo()调用,但是(&B::foo)(1)是合法的。

[注:对于具有显式对象形参的成员函数,对其取地址的结果类型为函数指针而非成员指针 ]

或者,对象类型与本类类型有继承关系,例如上文(2.2)的代码:

int main()
{
    D d;
    d.foo();
    return 0;
}

可以正常运行。

2.4 拥有显式对象形参的函数内部可以使用this吗?

不能。

2.5 既然如此,拥有显式对象形参的函数与其它普通成员函数在内部可以相互调用吗?

拥有显式对象形参的函数可以通过对象实参来调用其它普通成员函数,其它普通成员函数可以直接调用(或者以this调用)拥有显式对象形参的函数。

2.6 显式对象形参函数一定不能作为虚函数吗?

依据提案P0847R7.Explicit object parameter and virtual,暂时不允许,但不保证未来不会支持。

2.7 显式对象形参函数可以用于操作符重载吗?

可以。

2.8 显式对象形参可以用于lambda中吗?

可以,且lambda中的显式对象形参指代lambda自身,可以轻松地实现lambda的递归。

例如,提案P0847R7中的一段示例代码:

auto fib = [](this auto self, int n) {
    if (n < 2) return n;
    return self(n-1) + self(n-2);
};

而且,拥有显式对象形参lambda表达式与捕获this并不冲突,因为捕获的this指代lambda表达式所在的外部类对象。

todo: self声明为引用类型会如何

2.9 Deducing this引入了哪些新的问题?(等待翻译和解读)

4.2.5 The Shadowing Mitigation / Private Inheritance Problem

4.2.12 Code issues

3. Deducing this的使用实例

5 Real-World Examples

某些部分已经在提案中已经展现的相当清楚了(等待翻译和解读),以后主要会更新使用C++23的项目的代码实例。

4. 上文中与有关P0847R7的未提到的内容补充

附表 1

P0847R7 4.3 Not quite static, not quite non-static

If we go through several salient aspects of legacy static and legacy non-static member functions, we can see how explicit this functions fare:

legacy non-static explicit this legacy static
Requires/uses an object argument Yes Yes No
Has implicit this Yes No No
Can be used to declare operators Yes Yes No
Type of &C::f Pointer-to-Member Pointer-to-Function Pointer-to-Function
Does auto f = x.f; work? No No Yes
Can the name decay to a function pointer? No No Yes
Can it be virtual Yes Maybe No

如若还有疑问,参考P0847R7.Proposed Wording

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
隐藏
变装