专栏名称: CPP开发者
伯乐在线旗下账号,「CPP开发者」专注分享 C/C++ 开发相关的技术文章和工具资源。
目录
相关文章推荐
新智元  ·  Ilya又回来了!神秘初创SSI估值200亿 ... ·  昨天  
爱可可-爱生活  ·  【[99星]Ollama模型直链生成与安装工 ... ·  昨天  
爱可可-爱生活  ·  【[74星]EasyDeploy:一站式大规 ... ·  昨天  
爱可可-爱生活  ·  通俗版解读 查看图片-20250207090641 ·  2 天前  
宝玉xp  ·  GitHub Copilot 现在也支持 ... ·  2 天前  
51好读  ›  专栏  ›  CPP开发者

Mastering Placeholder Type Deduction

CPP开发者  · 公众号  ·  · 2024-05-08 09:40

正文

本篇可以结合 Left-to-Right vs. Right-to-Left Coding Styles 阅读,属于同一主题。本篇侧重于讲解具体的类型推导规则。

Decltype Specifier

在静态类型语言中,一个变量需要由类型说明符指定,而随着 C++ 的发展,类型也可以从表达式推导出来,不必显式写出。

一切始于 C++11 decltype(E) ,Decltype 也属于说明符,接受一个表达式参数。也可以传入一个变量,因为变量名属于 id-expressions ,也是表达式。

这里的核心在于,表达式其实包含三部分信息:type, value, 和 value category。使用 Decltype 推导出的表达式结果与原类型的信息并不总是相同,这种不一样的依据就是类型推导规则。

Decltype 的推导规则需要分为两种情况讨论, E (E) ,也就是说,多加一个括号将改变推导规则。

先来看第一种, E 的情况。

如果 E 是 id-expressions 或者类成员名称访问,此时推导结果的 type 和 value 都和 E 所对应的实体相同,但是不会保留原有的 value category。

1int a = 42;
2static_assert(std::is_same_v<decltype(a), int>);
3std::println("lvalue: {}"std::is_same_v<decltype(a), int&>);  // false
4std::println("prvalue: {}"std::is_same_v<decltype(a), int>);  // true
5std::println("xvalue: {}"std::is_same_v<decltype(a), int&&>); // false

a 是一个 lvalue,而 decltype(a) 是一个 prvalue。

再来看第二种, (E) 的情况。

如果 (E) 是 id-expressions 或者类成员名称访问,推导时 value 不变。对于 type,若 E 是 lvalue,推导的 type 为 T& ;若 E 是 xvalue,type 为 T&& 。同时也会保留 value category。

1int a = 42;
2static_assert(std::is_same_v<decltype((a)), int&>);
3std::println("lvalue: {}"std::is_same_v<decltype((a)), int&>);  // true
4std::println("prvalue: {}"std::is_same_v<decltype((a)), int>);  // false
5std::println("xvalue: {}"std::is_same_v<decltype((a)), int&&>); // false

a 是 lvalue,推导类型为 T& ,依旧是一个 lvalue。

核心就记住这两条规则即可,需要注意 (E) 推导的不只是实体的类型,还附加有实体所在的环境,就是规则中的 T& 所指,比如:

1struct




    
 A { double x; };
2const A* a;
3
4decltype(a->x) y;       // double
5decltype((a->x)) z = y; // const double&

再比如:

 1void f() {
2    float x, &r = x;
3    [=] {
4        decltype(x) y1; // float
5        decltype((x)) y2 = y1; // const float&
6
7        decltype(r) r1 = y1; // float&
8        decltype((r)) r2 = y2; // const float&
9    }
10}

由于 Lambda expressions 默认是不可修改的,因此使用 (x) 推导时会带上 const

到此为此,本文第一部分结束,接着让我们更进一步,看 Placeholder Type 的推导。

Placeholder Type

C++ 存在两种类型的 Placeholder Type 说明符, auto decltype(auto) 。使用这种类型的说明符,类型名称不必再显式指定,也不必使用 decltype() 根据表达式推导,一切推导都自动完成。它们也构成了 Modern C++ 的 Left-to-Right 声明风格。

刚开始, auto 仅是作为 Right-to-Left 风格的代替语法,以下两种声明意义完全相同。

1int f() {}
2auto f() -> int {}

这里只是换了一种语法形式,并不存在类型推导,返回类型由 trailing-return-type 显式指出。

若不显式从 trailing-return-type 指定,此时将推导类型。例如:

1auto f() {}
2decltype(auto) g() {}

它们两个的首要不同来源于语法, auto 可以和其他修饰符组合出现,如 const auto& ,而 decltype(auto) 必须单独出现,不能添加任何修饰符。

推导规则是另外一个不同点, auto 使用的是 TAD 规则,而 decltype(auto) 使用的是本文第一部分介绍的 decltype(E) 推导规则。

下面看具体的几条规则。

第一条规则, auto 推导时总是以 value 返回,不会返回引用,而 decltype(auto) 的规则支持动态返回。

 1auto f(int& a) {
2    return a;
3}
4
5decltype(auto) g(int& a) {
6    return a;
7 }
8
9int x = 42;
10static_assert(std::is_same_v<decltype(f(x)), int>);
11static_assert(std::is_same_v<decltype(g(x)), int&>);

示例中 f() 永远返回 int ,而 g() 可以返回 int& 。但是 auto 可以和修饰符组合使用,因此你也可以这样来返回引用:

1autof(int& a) {
2    return a;
3}
4
5int x = 42;
6static_assert(std::is_same_v<decltype(f(x)), int&>);

再看回 decltype(auto) ,推导起来其实相当于 decltype(a) ,类型就是实体 a 的类型。

TAD 的内容在 洞察函数重载决议 中已经详细讨论过,在此不再细述。需要注意, auto 使用 TAD 的规则推导,所以推导出来的类型也并不一定与原实体类型一致。例子:

1const int b = 0;
2auto c = b; // c is an int
3static_assert(std::is_same_v<decltype(c), int>);

这与 decltype(auto) 的行为完全不一致:

1const int b = 0;
2decltype(auto) c = b; // c is an int const
3static_assert(std::is_same_v<decltype(c), int const>);

只要谨记这条规则,就知道何时该使用哪种 Placeholder Type 了。

第二条规则,重定义函数,或是特化函数模板时,如果本身就使用的是 Placeholder Type,那么也应该使用相同的形式。

 1auto f();               // OK
2auto f() return 42; } // OK
3auto f();               // OK
4int f();                // error
5decltype(auto) f();     // error
6
7decltype(auto) g();               // OK
8decltype(auto) g() { return 42; } // OK
9decltype(auto) g();               // OK
10int g();                          // error
11auto g();                         // error

下面是一个函数模板的例子:

1template <class Tauto f(T t) { return t; } // #1
2template char f(char);                       // error, no matching template
3template auto f(int);                        // OK, return type is int
4template<> auto f(double);                   // OK, forward declration with unknown return type
5
6template <class TT f(T t) { return t; }    // OK, not functionally equivalent to #1
7template auto f(float);                      // OK, still matches #1
8template char f(char);                       // OK, now there is a matching template






请到「今天看啥」查看全文