Initializer list and structured bindings deduction ambiguity in C++17 - c++

I had always avoided initializations like the following
const auto& x = a, y = b;
const int* const x = ptr_1, *const y = ptr_2; // wot
For the reason that the reference and pointer qualifiers don't apply to both the initializations. Granted it's one of the first things beginners learn, the ambiguity associated with it makes me feel like the following is clearer and requires less thought on the reader's end
const auto& x = a;
const auto& y = b;
With C++17 and structured bindings I was happy and saw lots of potential. C++17 outlawed what C++14 and C++11 had failed to fix, auto x {1} is an int and not std::initializer_list<int>. But why does the following code not work?
const auto& [x, y] {a, b};
const auto& [x, y] = {a, b};
The latter is in line with the new rules for auto deduction and initializer lists, the expression on the right hand side is treated as an initializer list. But for the former compilation fails with the following error
initializer for variable '[a, b]' with type 'const auto &' contains multiple expressions
Is there any way I can declare both x and y with the structured bindings syntax without having to resort to tuples, pairs and the like? Also why is the former in the code example above ill formed code? Is there an ambiguity in that syntax?

Structured binding is, so to speak, for "unpacking" things. It's not designed to be a way to combine normal declarations. That const auto& applies to neither a nor b, despite the appearance.
Your particular attempt violates [dcl.dcl]/8:
A simple-declaration with an identifier-list is called a
structured binding declaration ([dcl.struct.bind]). [...] The initializer shall be of the form “= assignment-expression ”, of the form “{ assignment-expression }”, or
of the form “( assignment-expression )”, where the
assignment-expression is of array or non-union class type.
int a = 1, b = 2;
const auto bitand <:x, y:> = std::tie(a, b);
This structured binding declaration is (very) roughly equivalent to
const auto bitand __e = std::tie(a, b); // hidden variable
auto and x = std::get<0>(__e);
auto and y = std::get<1>(__e);
(The real thing uses tuple_element, not auto.)
Notes:
The const auto bitand applies to the hidden variable and only the hidden variable. x and y are always references even if you write just auto; whether their referent is const depends on the const propagation properties of the initializer's type.
A temporary materialized from a prvalue initializer will have its lifetime extended by the reference binding.
In this example, both x and y are of type "reference to int"; it is valid to write x = 1;.
There's special treatment for structured bindings in the decltype wording.
These semantics are unsurprising if we are talking about unpacking a struct, etc., with two "reference to int" members; a const on such things doesn't actually affect the referent's constness. OTOH, you are in for a bad surprise if you want to use structured binding declarations for something they aren't designed to do.

This syntax is just not supported. You can only unpack aggregate classes and objects which for which std::get has been overloaded: https://skebanga.github.io/structured-bindings/
Unfortunately, you cannot really make use of the cool deduction guide because you want a reference to a and not to the tuple member. Thus you have to write out the template parameter list.
#include <tuple>
int main()
{
int a = 1;
int b = 2;
const auto& [x, y] = std::tuple<int&,int&>{a, b};
}
You could also not be as stupid as me and read the docs correctly.
#include <tuple>
int main()
{
int a = 1;
int b = 2;
const auto& [x, y] = std::forward_as_tuple(a, b);
}
const auto& [x, y] = std::tie(a, b); works as well.

Related

Examples of mandatory type deduction in C++

Google's C++ Style Guide at some point states:
There are several contexts in which C++ allows (or even requires) types to be deduced by the compiler.
What are some examples of mandatory type deduction?
A simple example is initialisation of a lambda variable. The type of the lambda is anonymous, therefore it cannot be named explicitly and must be deduced:
auto var = [capture]{};
Another example:
struct {
int x, y;
} g_xy;
Here g_xy is a global variable of unnamed type. If you try to return it for example you have to let the compiler deduce the return type of the function because you cannot name it:
auto foo()
{
return g_xy;
}
Although possible unnamed types (except lamdas) are rarely useful and used.
The style guide gives several examples where the compiler does automatic type deduction. The most obvious case is whenever you use the auto keyword, introduced with C++11. auto is a placeholder for an actual type. Whenever you use auto the compiler will deduce the type from: the type of expression used to intialise a variable; the trailing type or type of return expression of a function.
Normally you would declare a variable like this:
int i = 0;
where you specify the type int for the variable i. However, in modern C++ you could declare variables without specifying their type and the compiler will deduce their types automatically:
auto a = 42; // a is an int
auto& b = a; // b is an int&
auto c = b; // c is an int
auto d{42}; // d is an int, not a std::initializer_list<int>
auto v = {1, 2, 3}; //v is a std::initializer_list<int>
Other examples include a named lambda function:
auto lower = [] (const char c) { return tolower(c); };
and in C++14 onward, a generic lambda where both the return type and lambda parameters can be auto:
auto add = [](const auto a, const auto b) { return a + b; }
One thing to note is that auto is a placeholder for type, not for const, volatile or reference specifers.
Some advantages of using auto include:
variable is always initialised
ensure the correct type is used without any implicit conversion
less typing and concern for actual type
And yet another example - type of generic lambdas with auto arguments can not be specified.
auto lam = [](auto v) { }
There is no way to specify type of v in invocation of lambda.

Why do structured bindings depend on tuple_element?

The most recent draft of the structured bindings proposal (on which the C++17 feature was based) requires std::tuple_size, member get or std::get, and std::tuple_element. Previous drafts require only std::tuple_size and member get or std::get. As far as I can tell, there was no discussion on adding this, it just appeared in the final draft. Is there a compelling reason to require the tuple_element specialization, considering I believe it can be implemented in general as
template<std::size_t index, typename T>
struct tuple_element {
using type = decltype(std::get<index>(std::declval<T>()));
};
Does anyone know of why this requirement was added?
Consider the case:
std::tuple<int, int&>& foo();
auto& [x, y] = foo();
What is decltype(x) and what is decltype(y)? The goal of the language feature is that x just be another name for foo().__0 and y be another name for foo().__1, which means that they should to be int and int&, respectively. As specificied today, this unpacks into†:
auto& __e = foo();
std::tuple_element_t<0, decltype(__e)>& x = std::get<0>(__e);
std::tuple_element_t<1, decltype(__e)>& y = std::get<1>(__e);
And the rules work such that decltype(x) is the type to which x refers, so int. And decltype(y) is the type to which y refers, so int&.
If we avoided tuple_element, by doing something like:
auto&& x = std::get<0>(__e);
auto&& y = std::get<1>(__e);
Then we couldn't differentiate between x and y, because there is no way to differentiate between what std::get<0>(__e) and std::get<1>(__e) do: both give back an int&.
This is also the way to add consistency between the above case and the normal struct case:
struct C {
int i;
int& r;
};
C& bar();
auto& [a, b] = bar();
We want, for the purposes of structured bindings, for a and b here to behave the same way as x and y there. And a and b here aren't introduced variables, they're just different names for __e.i and __e.r.
In the non-reference case, there is a different scenario where we cannot differentiate:
std::tuple<int, int&&> foo();
auto [x, y] = foo();
Here, we at present unpack via:
auto __e = foo();
std::tuple_element_t<0, decltype(e)>& x = std::get<0>(std::move(__e));
std::tuple_element_t<1, decltype(e)>& y = std::get<1>(std::move(__e));
Both std::get calls return an int&&, so you couldn't differentiate between them using auto&&... but the results of tuple_element_t are different - int and int&&, respectively. This difference could be seen with the normal struct case too.
†Note that due to CWG 2313, actually the unpacking happens into a uniquely named variable reference and the identifiers specified into the binding just refer to those objects.

Do structured bindings and forwarding references mix well?

I know I can do
auto&& bla = something();
and depending on the constness of the return value of something, I'd get a different type for bla.
Does this also work in the structured bindings case, e.g.
auto&& [bla, blabla] = something();
I would guess so (structured bindings piggy-back on auto initializers, which behave like this), but I can't find a definitive yes.
Update: Preliminary tests seem to do what I expect (derive the constness properly):
#include <tuple>
using thing = std::tuple<char, int*, short&, const double, const float&>;
int main()
{
char c = 0;
int i = 1;
short s = 2;
double d = 3.;
float f = 4.f;
thing t{c, &i, s, d, f};
auto&& [cc, ii, ss, dd, ff] = t;
c = 10;
*ii = 11;
ss = 12;
dd = 13.;
ff = 14.f;
}
Live demo, gives error as I'd expect if auto&& is doing its job:
main.cpp: In function 'int main()':
main.cpp:20:10: error: assignment of read-only reference 'dd'
dd = 13.;
^~~
main.cpp:21:10: error: assignment of read-only reference 'ff'
ff = 14.f;
I'd still like to know exactly where this behaviour is specified.
Note: Using "forwarding references" to mean this behaviour might be stretching it, but I don't have a good name to give the const deduction part of auto&& (or template-T&& for that matter).
Yes. Structured bindings and forwarding references mix well†.
In general, any place‡ you can use auto, you can use auto&& to acquire the different meaning. For structured bindings specifically, this comes from [dcl.struct.bind]:
Otherwise, e is defined as-if by
attribute-specifier-seqopt decl-specifier-seq ref-qualifieropt e initializer ;
where the declaration is never interpreted as a function declaration and the parts of the declaration other than the declarator-id are taken from the corresponding structured binding declaration.
There are further restrictions on these sections in [dcl.dcl]:
A simple-declaration with an identifier-list is called a structured binding declaration ([dcl.struct.bind]). The decl-specifier-seq shall contain only the type-specifier auto and cv-qualifiers. The initializer shall be of the form “= assignment-expression”, of the form “{ assignment-expression }”, or of the form “( assignment-expression )”, where the assignment-expression is of array or non-union class type.
Putting it together, we can break down your example:
auto&& [bla, blabla] = something();
as declaring this unnamed variable:
auto && e = something();
~~~~ ~~ ~~~~~~~~~~~
decl-specifier-seq initializer
ref-qualifier
The behavior is that is derived from [dcl.spec.auto] (specifically here). There, we do do deduction against the initializer:
template <typename U> void f(U&& );
f(something());
where the auto was replaced by U, and the && carries over. Here's our forwarding reference. If deduction fails (which it could only if something() was void), our declaration is ill-formed. If it succeeds, we grab the deduced U and treat our declaration as if it were:
U&& e = something();
Which makes e an lvalue or rvalue reference, that is const qualified for not, based on the value category and type of something().
The rest of the structured bindings rules follow in [dcl.struct.bind], based on the underlying type of e, whether or not something() is an lvalue, and whether or not e is an lvalue reference.
† With one caveat. For a structured binding, decltype(e) always is the referenced type, not the type you might expect it be. For instance:
template <typename F, typename Tuple>
void apply1(F&& f, Tuple&& tuple) {
auto&& [a] = std::forward<Tuple>(tuple);
std::forward<F>(f)(std::forward<decltype(a)>(a));
}
void foo(int&&);
std::tuple<int> t(42);
apply1(foo, t); // this works!
I pass my tuple is an lvalue, which you'd expect to pass its underlying elements in as lvalue references, but they actually get forwarded. This is because decltype(a) is just int (the referenced type), and not int& (the meaningful way in which a behaves). Something to keep in mind.
‡ There are two places I can think of where this is not the case.
In trailing-return-type declarations, you must use just auto. You can't write, e.g.:
auto&& foo() -> decltype(...);
The only other place I can think of where this might not be the case is part of the Concepts TS where you can use auto in more places to deduce/constrain types. There, using a forwarding reference when the type you're deducing isn't a reference type would be ill-formed I think:
std::vector<int> foo();
std::vector<auto> a = foo(); // ok, a is a vector<int>
std::vector<auto&&> b = foo(); // error, int doesn't match auto&&

Clarification regarding auto to constant reference

In this code:
int a=8;
const int &b = a; //b is a Low-Level const
auto c = b;
I expected c to be constant int type since it has been autoed to a low level const. But it is referring to a (which has been aliased by b) instead if b for its declaration.
c picks the b's type only when it is used like auto &c =b
What I understand is only top-level const is dropped. So can someone please explain what is wrong with my understanding?
Whereas in case of a pointer, the low level const is picked by auto:
int i=9;
const int * p=&i;
auto *d=p;
*d=45; // error: assignment of read-only location ‘* d’
.
The type of an expression is never a reference type. Expressions have non-reference types, and value categories.
The declaration const int &b = a; means:
There will be a variable called b.
The expression b has type const int.
The expression b designates the same object as a. In other words, the names a and b both refer to the same object.
The code decltype(b) resolves to const int&, but apart from that, you can't access the "history" of a reference. Once it's been bound, it's just a name for an int thereafter.
auto c = X; means that c is not declared as a reference; and its type is deduced from the the type of the expression X. If X happens to be the identifier of a variable, that does NOT invoke any special rules; X is treated as an expression in all cases.
The syntax decltype(auto) C = X; exists; if X is the name of a variable then this means that C is declared in the same way as X. (If X is not the name of a variable then the behaviour depends on the value category of X too).
In your first example, c is deduced from the type of the expression b which is const int. As you noted, top-level const is dropped here, so auto c means int c.
In your second example, the type of the expression p is const int *. This is not a top-level const, so auto d = p; would be const int * d = p;. In this case auto *d is not different to auto d.
Auto uses the template type deduction rules. The long and the short of it is that references get dropped when using auto (which is the reason you'll see things like decltype(auto) as returns for functions, as this uses a different set of rules for type deduction).
Scott Meyers goes through the rules in some detail in Item 2 of Effective Modern C++.
In the first example b and a are linked however c becomes the constant, this is because of the const in b taking effect. The problem with the second example is that *d is type const int which cannot be modified.
Hope this helps.

C++ auto keyword. Why is it magic?

From all the material I used to learn C++, auto has always been a weird storage duration specifier that didn't serve any purpose. But just recently, I encountered code that used it as a type name in and of itself. Out of curiosity I tried it, and it assumes the type of whatever I happen to assign to it!
Suddenly STL iterators and, well, anything at all that uses templates is 10 fold easier to write. It feels like I'm using a 'fun' language like Python.
Where has this keyword been my whole life? Will you dash my dreams by saying it's exclusive to visual studio or not portable?
auto was a keyword that C++ "inherited" from C that had been there nearly forever, but virtually never used because there were only two possible conditions: either it wasn't allowed, or else it was assumed by default.
The use of auto to mean a deduced type was new with C++11.
At the same time, auto x = initializer deduces the type of x from the type of initializer the same way as template type deduction works for function templates. Consider a function template like this:
template<class T>
int whatever(T t) {
// point A
};
At point A, a type has been assigned to T based on the value passed for the parameter to whatever. When you do auto x = initializer;, the same type deduction is used to determine the type for x from the type of initializer that's used to initialize it.
This means that most of the type deduction mechanics a compiler needs to implement auto were already present and used for templates on any compiler that even sort of attempted to implement C++98/03. As such, adding support for auto was apparently fairly easy for essentially all the compiler teams--it was added quite quickly, and there seem to have been few bugs related to it either.
When this answer was originally written (in 2011, before the ink was dry on the C++ 11 standard) auto was already quite portable. Nowadays, it's thoroughly portable among all the mainstream compilers. The only obvious reasons to avoid it would be if you need to write code that's compatible with a C compiler, or you have a specific need to target some niche compiler that you know doesn't support it (e.g., a few people still write code for MS-DOS using compilers from Borland, Watcom, etc., that haven't seen significant upgrades in decades). If you're using a reasonably current version of any of the mainstream compilers, there's no reason to avoid it at all though.
More recent revisions of the standard have added a few new places that auto can be used. Starting with C++14, you can use auto for the type of a parameter to a lambda:
[](auto s) { return s + 1; }
This does essentially the same thing as the example above--even though it doesn't explicitly use template syntax, this is basically a template that deduces the type of the parameter, and instantiates the template over that type.
That was convenient and useful enough that in C++20, the same capability was added for normal functions, not just lambdas.
But, just as before all of this really comes down to using the same basic type deduction mechanism as we've had for function templates since C++98. auto allows that to be used in more places, and more conveniently, but the underlying heavy lifting remains the same.
It's just taking a generally useless keyword and giving it a new, better functionality. It's standard in C++11, and most C++ compilers with even some C++11 support will support it.
For variables, specifies that the type of the variable that is being declared will be automatically deduced from its initializer. For functions, specifies that the return type is a trailing return type or will be deduced from its return statements (since C++14).
Syntax
auto variable initializer (1) (since C++11)
auto function -> return type (2) (since C++11)
auto function (3) (since C++14)
decltype(auto) variable initializer (4) (since C++14)
decltype(auto) function (5) (since C++14)
auto :: (6) (concepts TS)
cv(optional) auto ref(optional) parameter (7) (since C++14)
Explanation
When declaring variables in block scope, in namespace scope, in initialization statements of for loops, etc., the keyword auto may be used as the type specifier.
Once the type of the initializer has been determined, the compiler determines the type that will replace the keyword auto using the rules for template argument deduction from a function call (see template argument deduction#Other contexts for details). The keyword auto may be accompanied by modifiers, such as const or &, which will participate in the type deduction. For example, given const auto& i = expr;, the type of i is exactly the type of the argument u in an imaginary template template<class U> void f(const U& u) if the function call f(expr) was compiled. Therefore, auto&& may be deduced either as an lvalue reference or rvalue reference according to the initializer, which is used in range-based for loop.
If auto is used to declare multiple variables, the deduced types must match. For example, the declaration auto i = 0, d = 0.0; is ill-formed, while the declaration auto i = 0, *p = &i; is well-formed and the auto is deduced as int.
In a function declaration that uses the trailing return type syntax, the keyword auto does not perform automatic type detection. It only serves as a part of the syntax.
In a function declaration that does not use the trailing return type syntax, the keyword auto indicates that the return type will be deduced from the operand of its return statement using the rules for template argument deduction.
If the declared type of the variable is decltype(auto), the keyword auto is replaced with the expression (or expression list) of its initializer, and the actual type is deduced using the rules for decltype.
If the return type of the function is declared decltype(auto), the keyword auto is replaced with the operand of its return statement, and the actual return type is deduced using the rules for decltype.
A nested-name-specifier of the form auto:: is a placeholder that is replaced by a class or enumeration type following the rules for constrained type placeholder deduction.
A parameter declaration in a lambda expression. (since C++14) A function parameter declaration. (concepts TS)
Notes
Until C++11, auto had the semantic of a storage duration specifier.
Mixing auto variables and functions in one declaration, as in auto f() -> int, i = 0; is not allowed.
For more info : http://en.cppreference.com/w/cpp/language/auto
This functionality hasn't been there your whole life. It's been supported in Visual Studio since the 2010 version. It's a new C++11 feature, so it's not exclusive to Visual Studio and is/will be portable. Most compilers support it already.
The auto keyword is an important and frequently used keyword for C ++.When initializing a variable, auto keyword is used for type inference(also called type deduction).
There are 3 different rules regarding the auto keyword.
First Rule
auto x = expr; ----> No pointer or reference, only variable name. In this case, const and reference are ignored.
int y = 10;
int& r = y;
auto x = r; // The type of variable x is int. (Reference Ignored)
const int y = 10;
auto x = y; // The type of variable x is int. (Const Ignored)
int y = 10;
const int& r = y;
auto x = r; // The type of variable x is int. (Both const and reference Ignored)
const int a[10] = {};
auto x = a; // x is const int *. (Array to pointer conversion)
Note : When the name defined by auto is given a value with the name of a function,
the type inference will be done as a function pointer.
Second Rule
auto& y = expr; or auto* y = expr; ----> Reference or pointer after auto keyword.
Warning : const is not ignored in this rule !!! .
int y = 10;
auto& x = y; // The type of variable x is int&.
Warning : In this rule, array to pointer conversion (array decay) does not occur !!!.
auto& x = "hello"; // The type of variable x is const char [6].
static int x = 10;
auto y = x; // The variable y is not static.Because the static keyword is not a type. specifier
// The type of variable x is int.
Third Rule
auto&& z = expr; ----> This is not a Rvalue reference.
Warning : If the type inference is in question and the && token is used, the names
introduced like this are called "Forwarding Reference" (also called Universal Reference).
auto&& r1 = x; // The type of variable r1 is int&.Because x is Lvalue expression.
auto&& r2 = x+y; // The type of variable r2 is int&&.Because x+y is PRvalue expression.
The auto keyword specifies that the type of the variable that is being declared will be automatically deducted from its initializer. In case of functions, if their return type is auto then that will be evaluated by return type expression at runtime.
It can be very useful when we have to use the iterator. For e.g. for below code we can simply use the "auto" instead of writing the whole iterator syntax .
int main()
{
// Initialize set
set<int> s;
s.insert(1);
s.insert(4);
s.insert(2);
s.insert(5);
s.insert(3);
// iterator pointing to
// position where 2 is
auto pos = s.find(3);
// prints the set elements
cout << "The set elements after 3 are: ";
for (auto it = pos; it != s.end(); it++)
cout << *it << " ";
return 0;
}
This is how we can use "auto" keyword
It's not going anywhere ... it's a new standard C++ feature in the implementation of C++11. That being said, while it's a wonderful tool for simplifying object declarations as well as cleaning up the syntax for certain call-paradigms (i.e., range-based for-loops), don't over-use/abuse it :-)
It's Magic is it's ability to reduce having to write code for every Variable Type passed into specific functions. Consider a Python similar print() function in it's C base.
#include <iostream>
#include <string>
#include <array>
using namespace std;
void print(auto arg) {
cout<<arg<<" ";
}
int main()
{
string f = "String";//tok assigned
int x = 998;
double a = 4.785;
string b = "C++ Auto !";
//In an opt-code ASCII token stream would be iterated from tok's as:
print(a);
print(b);
print(x);
print(f);
}