你对C++的自动类型推导了解多少?
How well do you know C++ auto type deduction?

原始链接: https://www.volatileint.dev/posts/auto-type-deduction-gauntlet/

## C++ `auto` 类型推导:总结 C++ 的 `auto` 关键字通过自动推导变量类型来简化代码。基本的赋值会直接从初始化器推导出类型(例如,`auto v = 5;` 结果为 `int`)。然而,包含 `auto` 的表达式需要一致的类型;混合类型会导致编译错误。指针、`nullptr`,甚至函数指针都能被正确推导。 `auto` 会移除*顶层* CV 限定符(如 `const` 和 `volatile`),但会保留应用于指向类型的那些限定符。引用比较棘手:`auto` 本身不会推导引用,但 `auto&` 会,除非处理的是转发引用(`auto&&`),它可以绑定到左值和右值。 像 `decltype(auto)` 这样的高级特性提供了更多的控制。它会考虑表达式的本质来推导类型——左值会产生引用,纯右值会产生其直接类型。继承和结构化绑定引入了进一步的细微差别,通常涉及隐藏的拷贝或别名。使用 `decltype(auto)` 的 Lambda 捕获尤其复杂,受捕获模式(按值 vs. 按引用)和可变性的影响。 理解这些规则对于编写高效且正确的 C++ 代码至关重要。

黑客新闻 新的 | 过去的 | 评论 | 提问 | 展示 | 工作 | 提交 登录 你对 C++ 的 auto 类型推导了解多少? (volatileint.dev) 7 分,volatileint 1 小时前 | 隐藏 | 过去的 | 收藏 | 1 条评论 CalChris 7 分钟前 [–] 显然 C++ 标准称之为类型推导。但我一直称之为类型推断。回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

One of the most iconic C++ features is the language’s ability to deduce types with the auto keyword. In this post, I’ll give a series of code snippits. Your job is to assess what will be deduced for v in each case. Determine for each:

  1. The deduced type
  2. If it is a value, an lvalue or rvalue reference, or a pointer
  3. Which CV qualifiers are applicable

Some of these may not even compile, so “this won’t work” is a totally valid answer.

Each section increases in difficulty. Good luck!

Basics

Basic assignments and deduction from constants and straightforward types.


auto v = 5;
Answer

Type: int
Explanation: Straightforward type deduction from an integer constant.


auto v = 0.1;
Answer

Type: double
Explanation: Notably different than integers. Floating points default to the larger double instead of float.


int x;
auto v = x;
Answer

Type: int
Explanation: Simple type derived from the assigned-from variable.


auto v = 5, w = 0.1;
Answer

Type: Fails to compile.
Explanation: All types in an expression defined with auto have to be the same.


int x;
auto v = &x;
Answer

Type: int*
Explanation: Auto will deduce pointers.


auto v = nullptr;
Answer

Type: std::nullptr_t
Explanation: nullptr has its own type.


auto v = { 1, 2, 3 };
Answer

Type: std::initializer_list<int>
Explanation: It might seem like this should create a container, but it won’t!


int x[5];
auto v = x;
Answer

Type: int*
Explanation: C-style arrays decay to a pointer. The decay happens before auto is evaluated.


int foo(int x) {
    return x;
}
auto v = foo;
Answer

Type: int (*) (int)
Explanation: auto can deduce function pointers.

Exploring how references and CV-qualifiers are handled.


volatile const int x = 1;
auto v = x;
Answer

Type: int
Explanation: auto drops top-level CV qualifiers.


volatile const int x = 1;
auto v = &x;
Answer

Type: volatile const int*
Explanation: CV qualifiers applied to pointed-to or referred-to types are maintained.


int x;
int& y = x;
auto v = y;
Answer

Type: int
Explanation: auto will never deduce a reference on its own.


int x;
auto& v = x;
Answer

Type: int&
Explanation: lvalue references are deduced via auto&.


int x[5];
auto& v = x;
Answer

Type: int (&) [5]
Explanation: When binding arrays to references, they don’t decay. So auto deduces the actual array type.


int foo(const int x) {
    return x;
}
auto v = foo;
Answer

Type: int (*) (int)
Explanation: Remember - CV qualifiers on parameters are thrown away during function resolution!

Advanced

Forwarding references, decltype(auto), inheritance, structured binding, and lambdas.


int x;
auto&& v = x;
Answer

Type: int&
Explanation: A forwarding reference like auto&& can bind to lvalue or rvalue expressions. Here we get an lvalue reference because x is an lvalue.


auto x = [] () -> int { 
    return 1;
};
auto&& v = x();
Answer

Type: int&&
Explanation: x() returns a prvalue, and prvalues assigned to forwarding references yield an rvalue reference.


int x;
auto y = [&] () -> int& { 
    return x;
};
auto&& v = y();
Answer

Type: int&
Explanation: This time x() returns an lvalue, and lvalues assigned to forwarding references yields an lvalue reference.


int x;
decltype(auto) v = (x);
Answer

Type: int&
Explanation: (x) is an expression. decltype(expression) yields an lvalue reference when the expression is an lvalue.


struct Foo {};
auto&& v = Foo{};
Answer

Type: Foo&&
Explanation: prvalues like Foo{} will bind to an rvalue reference.


struct Foo {};
decltype(auto) v = Foo{};
Answer

Type: Foo
Explanation: For any prvalue expression e, decltype(e) evaluates to the type of e.


int x;
decltype(auto) v = std::move(x);
Answer

Type: int&&
Explanation: For any xvalue expression e, decltype(e) evalutes to an rvalue reference to the type of e.


int foo(int x) {
    return x;
}
decltype(auto) v = foo;
Answer

Type: Fails to compile.
Explanation: Function id-expressions do not decay to pointers when evaluating with decltype!


int foo(int x) {
    return x;
}
decltype(auto) v = (foo);
Answer

Type: int (&) (int)
Explanation: Parenthesized function symbol expressions are deduced as a reference to the function.


class Base {
    public:
        auto foo() {
            return this;
        };
};

class Derived : public Base {
};

Derived d;
auto v = d.foo();
Answer

Type: Base*
Explanation: foo is defined in the Base class, so this has to refer to a Base*, even when foo is called from a child class.

Oof

Abandon all hope, ye who attempt to deduce the types of lambda captures in expressions with decltype(auto) and symbols defined via structured binding.


int x;
[&] {
    decltype(auto) v = x;
}();
Answer

Type: int
Explanation: decltype(auto) always deduces id-expressions strictly with the type of the target symbol. Even though x is captured by reference, it is not deduced as a reference.


int x;
[&] {
    decltype(auto) v = (x);
}();
Answer

Type: int&
Explanation: However, lvalues in parenthesized expressions are deduced as references via decltype(auto).


int x;
[=] {
    decltype(auto) v = x;
}();
Answer

Type: int
Explanation: lvalue id-expressions are deduced strictly as their type via decltype(auto).


int x;
[=] {
    decltype(auto) v = (x);
}();
Answer

Type: const int&
Explanation: Captures by value are const, so the type of x is const int. lvalues in a parenthesized expression are deduced as references - so we end with const int&.


int x;
[=] mutable {
    decltype(auto) v = (x);
}();
Answer

Type: int&
Explanation: Unless, of course, we make the lambda mutable. Then the captures are non-const.


int x;
int& y = x;
[=] () {
    decltype(auto) v = y;
}();
Answer

Type: Fails to compile.
Explanation: Lambdas cannot capture references. When y is captured, it captures the referred-to value x. Because captures are const, the captured value ends up as const int. However, decltype(auto) sees the symbol y’s declaration as an int& and deduces the type of v as int&. Compilation fails on discarding the const qualifier when trying to assign a const int to an int&.


std::pair x {1, 2.0};
auto [v, w] = x;
Answer
Type: std::tuple_element<0, std::pair<int, float> >::type& (but which presents as an int in basically every observable way)
Explanation: The GodBolt linked at the bottom of this thread will print this is an int beacuse of special rules around how decltype works with structured binding. In reality, it is an alias of a hidden copy of x. See this Reddit thread for why the original version of this quiz - which asserted this was an int - was wrong.

You can check any of these by using this GodBolt link. Shoutout to this Stack Overflow thread which provided the code snippit to print human readable names for a variable.

Do you have any interesting examples to share? Reach out at [email protected] and let me know! If you found this article interesting, consider subscribing to the newsletter to hear about new posts!

联系我们 contact @ memedata.com