惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
L
LINUX DO - 热门话题
月光博客
月光博客
B
Blog
博客园 - 叶小钗
美团技术团队
D
Docker
A
About on SuperTechFans
Stack Overflow Blog
Stack Overflow Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Y
Y Combinator Blog
V
V2EX
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 三生石上(FineUI控件)
The Register - Security
The Register - Security
博客园_首页
The Cloudflare Blog
I
InfoQ
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
Engineering at Meta
Engineering at Meta
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
C
CERT Recently Published Vulnerability Notes
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
V
Visual Studio Blog
博客园 - Franky
Cloudbric
Cloudbric
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Webroot Blog
Webroot Blog
博客园 - 【当耐特】
TaoSecurity Blog
TaoSecurity Blog
B
Blog RSS Feed
N
News | PayPal Newsroom
人人都是产品经理
人人都是产品经理
H
Heimdal Security Blog
L
LangChain Blog
PCI Perspectives
PCI Perspectives
Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
Schneier on Security
Schneier on Security

核桃的炼金工坊

2023 韩国游记 C++23: Flat Containers Stateful Metaprogramming 推し、燃ゆ Customization Point Object 2020 总结 搞了个 C++ 构建系统 软件设计哲学(NOTE) Paxos Note 关于 cpp 可见性的黑魔法后门 一个关于 private member function detect 的 SFINAE 模板 User-defined conversion and Copy elision VIM and Latex Compare Between CRTP and Virtual Interface in C++ Compile Time Reflection in C++11 C++11内存模型 在C++17中的部分新特性 Const Reference of Pointer
Deducing This
Hawtian Wang · 2023-09-17 · via 核桃的炼金工坊

A new way of declaring non-static member functions that will allow for deducing the type and value category of the class instance parameter while still being invocable with regular member function syntax.

简单的说,就是让非静态成员函数的 this 参数成为一个显式参数。而不破坏以前的调用成员函数的语法。

The new syntax is to use an explicit this-annotated parameter.

一个非静态成员函数可以把它的第一个参数成为一个显式对象参数 (explicit object parameter),用一个关键字前缀 this 来表示。 这个参数可以像其他参数一样,使用 cv-ref- 限定符。那么当然也可以用模板。

struct Foo {
    void foo(this const Foo&);
    void foo(this volatile Foo&&);
    template<typename Self>
    void bar(this Self self);
    void barbar(this auto self);
};

非静态成员函数,隐式决定对象类型和显式对象参数类型的表示:

Implicit objectExplicit Object Parameter
void foo() &;void foo(this X&);
void foo() const &;void foo(this const X&);
void foo() &&;void foo(this X&&);

在调用一个这样的成员函数的时候,这个对象会作为第一个参数,其他参数会依次推后一个。 ( 比如括号里的第一个参数,实际是声明里的第二个参数。 )

考虑下面两个不同的函数声明,他们的类型分别为:

struct Y {
    int f(int, int) const&;
    int g(this Y const&, int, int);
};
  • &Y::f is int(Y::*)(int, int) const&
  • &Y::g is int(*)(const Y&, int, int)

也就是说,带有 explicit object parameter 的函数可以理解成被视作静态成员函数的。 所以静态成员函数也不能有 explicit object parameter

实际上带有 explicit object parameter 不同与非静态成员函数和静态成员函数, 是另一种成员函数类型。

They’re like semi-static member functions. We’ll call them explicit object member functions due to them having an explicit object parameter.

非静态EOMF静态
Requires/uses an object argumentYesYesNo
隐式声明了 thisYesNoNo
可以声明重载运算符YesYesNo
&C::f 的类型成员函数指针函数指针函数指针
虚函数?YesMaybeNo
auto f = x.f;NoNoYes
名字可以退化成函数指针NoNoYes

2.2 Candidate Functions

有了 Deducing this 之后,新加入了带有 explicit object parameter 的 candidates。

The only change in how we look up candidate functions is in the case of an explicit object parameter, where the argument list is shifted by one. The first listed parameter is bound to the object argument, and the second listed parameter corresponds to the first argument of the call expression.

由于带 explicit object parameter 的函数会被视作静态函数,并且在参与重载决议的时候, 参数被视作从第二个参数开始。所以下面这两个函数定义是冲突的:

static void foo();
void foo(this X const&);

类似 std::optional<T> 的例子:

template<typename T>
struct Optional {
    auto value() const & {
        if (has_value()) {
            return value_;
        }
        throw std::bad_optional_access();
    }
    auto value() const&& {
        if (has_value()) {
            return std::move(value_);
        }
        throw std::bad_optional_access();
    }
    auto value() && {
        if (has_value()) {
            return std::move(value_);
        }
        throw std::bad_optional_access();
    }
    auto value() & {
        if (has_value()) {
            return value_;
        }
        throw std::bad_optional_access();
    }
};
template<typename T>
struct Optional {
    auto value(this auto&& self) {
        if (self.has_value()) {
            return std::forward_like<decltype(self)>(self.value_);
        }
        throw std::bad_optional_access();
    }
};

lambda 一直都有个问题是递归比较困难,因为没法拿到 lambda 函数自己这个对象。 所以一般都是用些 trick 的方法。要不就是转成一个 std::function 的对象存起来, 要不就是要在每次调用的时候,都把自己作为一个参数传给自己,像这样:

std::function<int(int)> fib;
fib = [&fib](int i) {
    if (i < 2) return 1;
    return fib(i - 1) + fib(i - 2);
};

auto fib2 = [](auto& fib, int i) {
    if (i < 2) return 1;
    return fib(i - 1) + fib(i - 2);
};

std::cout << fib2(fib2, 10);
auto fib = [](this auto self, int i) {
    if (i < 2) return 1;
    return self(i - 1) + self(i - 2);
};

Update 2023.11.8

这个递归甚至可以被用在 std::visit 里面:

struct Leaf {};
struct Node;
using Tree = std::variant<Leaf, Node*>;
struct Node {
  Tree left, right;
};

template <typename... Ts>
struct overload : Ts... { using Ts::operator()...; }

int countLeaves(const Tree& tree) {
  return std::visit(overload{
      [] (const Leaf&) { return 1; },
      [] (this const auto& self, const Node* node) -> int {
        return visit(self, node->left) + visit(self, node->right);
      }
    },
    tree);
}

CRTP 一个重要的部分,就是要在基类里得到子类的类型,这个一般都是把基类变成一个模板类, 然后在继承的时候把子类类型传进去得到的。有 Deducing this 之后,explicit object member function 的 explicit object parameter 是模板的话,就可以在调用的时候得到实际子类的类型。

template<typename D>
struct CRTP {
    void foo() {
        static_cast<D*>(this)->fooImpl();
    }
};
struct CRTP {
    void foo(this auto& self) {
        self.fooImpl();
    }
};
struct Builder {
    Builder name(this Builder self, auto value) { self.name_ = std::move(value); return self; }
    Builder password(this Builder self, auto value) { self.password_ = std::move(value); return self; }
};

对于像 std::string_view 这类的小对象,传值是个比传引用更快,所以应该更多传值而不是传引用, 对于这类的对象。但是在以前一个函数调用会把 *this 作为一个参数传着。对于这种对象, 把 *this 变成 Self 显然是个很大的性能优化。

struct StringView {
    auto length(this Stringview self) { return self.length; }
};

  1. C++ Draft: Duducing this