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:
- The deduced type
- If it is a value, a lvalue or rvalue reference, or a pointer
- 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!
Enter the Gauntlet
Basics
Basic assignments and deduction from constants and straightforward types.
auto v = 5;
Answer
intExplanation: Straightforward type deduction from an integer constant.
auto v = 0.1;
Answer
doubleExplanation: Notably different than integers. Floating points default to the larger
double instead of float.
int x;
auto v = x;
Answer
intExplanation: Simple type derived from the assigned-from variable.
auto v = 5, w = 0.1;
Answer
Explanation: All types in an expression defined with
auto have to be the same.
int x;
auto v = &x;
Answer
int*Explanation: Auto will deduce pointers.
auto v = nullptr;
Answer
std::nullptr_tExplanation:
nullptr has its own type.
auto v = { 1, 2, 3 };
Answer
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
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
int (*) (int)Explanation:
auto can deduce function pointers.Intermediate
Exploring how references and CV-qualifiers are handled.
volatile const int x = 1;
auto v = x;
Answer
intExplanation:
auto drops CV qualifiers.
volatile const int x = 1;
auto v = &x;
Answer
volatile const int*Explanation: Unless, of course, they are applied to a pointed-to or reference type
int x;
int& y = x;
auto v = y;
Answer
intExplanation:
auto will never deduce a reference on its own.
int x;
auto& v = x;
Answer
int&Explanation: lvalue references are deduced via
auto&.
int x[5];
auto& v = x;
Answer
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
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
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
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
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
int&Explanation:
(x) is an expression. decltype(expression) yields a rvalue reference when the expression is an lvalue.
struct Foo {};
auto&& v = Foo{};
Answer
Foo&&Explanation: prvalues like Foo{} will bind to an rvalue reference.
struct Foo {};
decltype(auto) v = Foo{};
Answer
FooExplanation: For any prvalue expression
e, decltype(e) evaluates to the type of e.
int x;
decltype(auto) v = std::move(x);
Answer
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
Explanation: Function id-expressions do not decay to pointers when evaluating with
decltype!
int foo(int x) {
return x;
}
decltype(auto) v = (foo);
Answer
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
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).
int x;
[&] {
decltype(auto) v = x;
}();
Answer
intExplanation:
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
int&Explanation: However, lvalues in parenthesized expressions are deduced as references via
decltype(auto).
int x;
[=] {
decltype(auto) v = x;
}();
Answer
intExplanation: lvalue id-expressions are deduced strictly as their type via
decltype(auto).
int x;
[=] {
decltype(auto) v = (x);
}();
Answer
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
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
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&.One last really tricky to correctly answer:
std::pair x {1, 2.0};
auto [v, w] = x;
Answer
x)Explanation: The GodBolt linked at the bottom of this thread will print this is a copy. In reality, it is an alias of a hidden copy of
x. There is active discussion on the right answer in this Reddit thread, but my original version of this post stating that v is an int is, at best, an oversimplification.Conclusion
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 sam@volatileint.dev and let me know! If you found this article interesting, consider subscribing to the newsletter to hear about new posts!