You've answered 1 of 180 questions correctly. (Clear)
Question #313 Difficulty:
According to the C++23 standard, what is the output of this program?
#include <iostream>
void f(float &&) { std::cout << "f"; }
void f(int &&) { std::cout << "i"; }
template <typename... T>
void g(T &&... v)
{
(f(v), ...);
}
int main()
{
g(1.0f, 2);
}
Correct!
Explanation
The function template g
instantiates to void g<float,int>(float&& v1, int&& v2) { f(v1), f(v2); }
, which sequences the call to f(v1)
before f(v2)
. However, f(v1)
(the float
) calls the int&&
overload, and f(v2)
(the int
) calls the float&&
overload!
f(v1)
can't call the float&&
overload, because that would bind an rvalue reference to an lvalue. It can, however, call the int&&
overload by converting the float to a prvalue int
. So the float
call calls the int
overload and vice versa.
Detailed explanation
First, the template parameter pack is deduced as float, int
, and the function parameter pack (T &&... v)
is expanded to (float&& v1, int&& v2)
, since 1.0f
is a floating-point literal and 2
is an integer literal.
Inside g
, the named rvalue references v1
and v2
are treated as lvalues. §[basic.lval]¶note-3:
In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues (...).
The body of g
consists of the fold-expression (f(v), ...)
which is a unary right fold with f(v)
as a cast-expression and ,
as a fold-operator. §[expr.prim.fold]¶2:
An expression of the form (... op e) where op is a fold-operator is called a unary left fold. An expression of the form (e op ...) where op is a fold-operator is called a unary right fold.
The instantiation of the unary right fold produces f(v1), f(v2)
. §[temp.variadic]¶10.2:
The instantiation of a fold-expression (§[expr.prim.fold]) produces:
— (...)
— E1 op (... op (EN-1 op EN)) for a unary right fold,
Now we need to do overload resolution for f
, based on the arguments in the two calls. First of all, int&&
is reference-related to int&&
, and float&&
is reference-related to float&&
.
Given types “cv1
T1
” and “cv2T2
”, “cv1T1
” is reference-related to “cv2T2
” ifT1
is similar (§[conv.qual]) toT2
, orT1
is a base class ofT2
.
Two types
T1
andT2
are similar if they have qualification-decompositions with the same n such that corresponding Pi components are either the same or one is “array of Ni” and the other is “array of unknown bound of”, and the types denoted byU
are the same.
A qualification-decomposition of a type
T
is a sequence of cvi and Pi such thatT
is“cv0 P0 cv1 P1 ... cvn-1 Pn-1 cvn
U
” for n ≥ 0,where each cvi is a set of cv-qualifiers (§[basic.type.qualifier]), and each Pi is “pointer to” (§[dcl.ptr]), “pointer to member of class Ci of type” (§[dcl.mptr]), “array of Ni”, or “array of unknown bound of” (§[dcl.array]).
Since T1
is reference-related to T2
, we can't initialize the rvalue reference with an lvalue. §[dcl.init.ref]¶5.4.4:
If
T1
is reference-related toT2
:— (...)
— if the reference is an rvalue reference, the initializer expression shall not be an lvalue.
This means we can't directly bind the reference to the initializer expression here. We can however implicitly convert the float
to a prvalue int
using a standard conversion sequence (floating-integral conversion) and bind the int &&
to it.
To get the "intuitively expected" order of the function calls we can simply use std::forward
and rewrite fold-expression like this: (f(std::forward<decltype(v)>(v)), ...);
.
You can explore this question further on C++ Insights or Compiler Explorer!
Mode : Training
You are currently in training mode, answering random questions. Why not Start a new quiz? Then you can boast about your score, and invite your friends.
Contribute
Android app
Get Sergey Vasilchenko's CppQuiz Android app.
C++ Brain Teasers
Get the book, support the site!