Using auto for a defined function - c++

I've used auto to store a lambda that is constructed right in the auto assignment, but today I was looking at this interesting paper on functional programming using c++ templates and came across this code:
template < typename T , typename Ops >
T fold ( Linked <T > * p )
{
T acc = Ops :: initial () ;
while ( p ) {
acc = Ops :: bin ( acc , p - > head ) ;
p = p - > tail ;
}
return acc ;
}
// later, in main():
auto sumup = fold <int , IntOps >;
I am trying to understand what the type of sumup would be, since it is assigned not to the output of fold but rather to the actual function fold itself! I decided to take a look at the various ways auto is shown to be used here. I am assuming that this use of auto falls under (1) on that page, a general variable initializer. What is not clear is what is the type of sumup?
And, would auto potentially be the same here as doing this:
using functionType = int (Linked<int>*);
functionType sumup = fold <int , IntOps >;
This is probably not correct, but I'd be curious if my thinking is in the right direction. When instantiated, fold <int , IntOps > will be a function that returns an int and taking a single argument of Linked<int>*, so my using declaration is saying the same thing? Is this using declaration a bonafide "type" and is the auto arriving at the same deduction as this using?

While every function has a type, you cannot have expressions of that type, nor variables. So int foo(float) has type int(float), for instance, but you cannot have a variable of that type.
You can have expressions and variables of type pointer to function, so int(*)(float). For instance, &foo is such a pointer to function.
The two types are quite closely related, obviously. In fact, the conversion is implicit: int (*pFoo)(float) = foo; does the conversion automatically.
What you do here is pretty much the same: int (*sumup)(Linked<int>*) = fold <int , IntOps >;. You see that auto makes the definition much more readable.

auto works by the same rules as template argument deduction. That is, when unqualified, it will take things by value. Since here you're returning a function reference, it will have to decay down to a pointer, because there's no "value type" for a function, with a specific size and whatnot.
You could also capture with auto&& which would make the type of sumup be int (&)(Linked<int>*), i.e. a reference to the function.

Related

Type of recursive calling generic lambda expression in C++14

What is the type of the lambda expression in (1) ?
Why can this code compile?
#include<functional>
#include<iostream>
int main() {
std::cout <<
[](auto&& f0,auto&& a0){return f0(f0,a0);}
(
[](auto& f,auto&& a)->int{ return (a>1) ? f(f,a-1)*a : 1; }, // (1)
5
)
<< std::endl;
}
I think that infinite recursion is caused by type inference for lambda expression (1) in this code.
I think that auto& f is replaced to a type name such as std::function<int(std::function<int(std::function<int(......)>)>)>.
Please point out my mistake.
First mistake: std::function is a type unrelated to any lambda.
A lambda is an anonymous type with an operator() and a few other known properties.
std::function<R(Args...)> is a type erasure class for copy construct, destroy and invoke with Args... and return R. It can be constructed from a lambda, but is not otherwise a related type.
As you cannot name the type of a lambda, using a std::function to store it is common. The lambda is not a std::function however. std::functions have nearly unavoidable overhead from their type erasure and polymorphism: lambdas lack any polymorphism, which makes it really easy for the compiler to understand what () does at the point of invocation.
In your case you have two lambdas.
Your first lambda is:
[](auto&& f0,auto&& a0){return f0(f0,a0);}
This looks like a form of y-combinator, or a variant, used help with recursion. The operator() in this case has signature:
template<class F0, class A0>
auto operator()(F0&&,A0&&)const
-> std::result_of_t<F0&(F0&,A0&)>
roughly.
A more useful version (in my opinion) is:
[](auto&& f0){
return [f0=std::forward<decltype(f0)>(f0)]
(auto&&...args) {
return f0(f0, std::forward<decltype(args)>(args)...);
};
}
which takes an f0, stores it, and invokes it with any arguments passing f0 first. This lets you bind the recursion 'out of sight'. Making the inner lambda mutable is optional (depends if you want to invoke in a const context)
Anyhow, the next lambda:
[](auto& f,auto&& a)->int{ return (a>1) ? f(f,a-1)*a : 1; }
has an operator() signature of:
template<class F, class A>
auto operator()(F&,A&&)const
-> int
You then pass an instance of the second lambda to the first, plus an argument, and it calculates n!.
The types deduced by the template operator () do not depend on the types that the arguments themselves deduce, so there is no infinite type deduction problem. The return type of the inner lambda is hard coded to int, so you don't have to deduce what () recursively returns to know it returns int.
If you want to store the first lambda in a std::function, however, you are going to be disappointed. std::function cannot erase a template operator(): it can only erase a fixed signature, and a template member is a factory of methods, not a method itself.
However, remember my better version of y combination above?
Call your first lambda g, your second h and my lambda y and the lambda my lambda returns z.
Then g(h,x) = y(h)(x) -- and y(h) can be stored in a std::function<int(int)> no problem. We hide the part of the recursion that basically requires a recursive type signature, which std::function does not support1. What is left, while it has a template operator(), can be bound to a simple signature.
1 note that you could write std::function to support recursive signatures, like std::function< std::vector<SELF_TYPE>(int) >. You can see how this might work with how boost::variant works with recursive variants.
From [expr.prim.lambda], emphasis mine:
The
lambda return type is auto, which is replaced by the trailing-return-type if provided and/or deduced from
return statements as described in 7.1.6.4.
You provide a trailing-return-type, that is the ->int in your code, so no type deduction has to happen. The return type is just int.
However, even without the ->int, you can still get your function to compile if you just provided a if statement instead of using the conditional operator:
auto f = [](auto& f0, auto&& a) {
if (a <= 1) {
return 1; // this *must* be the first return case.
}
else {
return f0(f0, a-1) * a;
}
};
std::cout << f(f, 5) << std::endl; // prints 120
This case, and only this case, fits one of the rules as above mentioned in §7.1.6.4 [dcl.spec.auto]:
If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression,
the program is ill-formed. Once a return statement has been seen in a function, however, the return type
deduced from that statement can be used in the rest of the function, including in other return statements.
[Example:
auto sum(int i) {
if (i == 1)
return i; // sum’s return type is int
else
return sum(i-1)+i; // OK, sum’s return type has been deduced
}
—end example ]

What is the type of this self-applying factorial function?

I wrote an anonymous factorial function in C++ and compiled my code with g++4.9.2.
It works well. However, I don't know the type of my function.
#include<iostream>
#include<functional>
using std::function;
int main()
{
//tested at g++ 4.9.2
//g++ -std=c++1y -o anony anony.cpp
auto fac = [](auto self,auto n)->auto{
if(n < 1)
return 1;
else
return n * self(self,n-1);
};
std::cout<<fac(fac,3)<<std::endl;//6
return 0;
}
So, I wonder: what are the types of fac and self?
If I just translate the C++ code into Haskell, it won't compile because
it involves infinite types:
fac2 self 0 = 1
fac2 self n = n * (self self $ n-1)
and I have to define some recursive type work around it:
data Y a = Y ((Y a)->a->a)
fac2 self 0 = 1
fac2 self n = n * ((applY self self) (n-1))
where applY (Y f1) f2 = f1 f2
fact2 = fac2 $ Y fac2
So, why could g++ get exactly the right type of the fac function, and what type does g++ think the fac function is?
The C++ fac isn't really a function, but a struct which has a member function.
struct aaaa // Not its real name.
{
template<typename a, typename b>
auto operator()(a self, b n) const
{
}
};
The overloaded call operator hides some of the trickery that C++ performs in order to implement "lambda functions"
When you "call" fac, what happens is
fac.operator() (fac, 3);
so the argument to the function isn't the function itself, but an object which has it as a member.
One effect of this is that the function's type (i.e. the type of operator()) does not occur in the type of the operator() function itself.
(The type of self is the struct that defines the function.)
The template part isn't necessary for this to work; this is a non-generic version of the fac "function":
struct F
{
int operator()(const F& self, int n) const
{
// ...
}
};
F fac;
fac(fac, 3);
If we keep the template and rename operator() to applY:
// The Y type
template<typename a>
struct Y
{
// The wrapped function has type (Y<a>, a) -> a
a applY(const Y<a>& self, a n) const
{
if(n < 1)
return 1;
else
return n * self.applY(self, n-1);
}
};
template<typename a>
a fac(a n)
{
Y<a> y;
return y.applY(y, n);
}
we see that your working Haskell program and your C++ program are very similar - the differences are mainly punctuation.
In contrast, in Haskell
fac2 self 0 = 1
fac2 self n = n * (self self $ n-1)
self is a function, and fac2's type would have to be
X -> Int -> Int
for some X.
Since self is a function, and self self $ n-1 is an Int, self's type is also X -> Int -> Int.
But what could X be?
It must be the same as the type of self itself, i.e X -> Int -> Int.
But that means that the type of self is (substituting for X):
(X -> Int -> Int) -> Int -> Int
so the type X must also be
(X -> Int -> Int) -> Int -> Int
so self's type must be
((X -> Int -> Int) -> Int -> Int) -> Int -> Int
and so on, ad infinitum.
That is, in Haskell the type would be infinite.
Your solution for Haskell essentially explicitly introduces the necessary indirection that C++ generates through its structure with a member function.
As others pointed out, the lambda acts as a structure involving a template. The question then becomes: why Haskell can not type the self-application, while C++ can?
The answer lies on the difference between C++ templates and Haskell polymorphic functions. Compare these:
-- valid Haskell
foo :: forall a b. a -> b -> a
foo x y = x
// valid C++
template <typename a, typename b>
a foo(a x, b y) { return x; }
While they might look nearly equivalent, they are not really such.
When Haskell type checks the above declaration, it checks that the definition is type safe for any types a,b. That is, if we substitute a,b with any two types, the function must be well-defined.
C++ follows another approach. At template definition, it is not checked that any substitution for a,b will be correct. This check is deferred to the point of use of the template, i.e. at instantiation time. To stress the point, let's add a +1 in our code:
-- INVALID Haskell
foo :: forall a b. a -> b -> a
foo x y = x+1
// valid C++
template <typename a, typename b>
a foo(a x, b y) { return x+1; }
The Haskell definition will not type check: there's no guarantee you can perform x+1 when x is of an arbitrary type. The C++ code is fine, instead. The fact that some substitutions of a lead to incorrect code is irrelevant right now.
Deferring this check causes some "infinitely-typed values" to be allowed, roughly. Dynamic languages such as Python or Scheme further defer these type errors until run-time, and of course will handle self-application just fine.
The expression following auto fac = is a lambda expression, and the compiler will automatically generate a closure object from it. The type of that object is unique and known only to the compiler.
From N4296, §5.1.2/3 [expr.prim.lambda]
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type — whose properties are described below. This class type is neither an aggregate (8.5.1) nor a literal type (3.9). The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression.
Note that because of this, even two identical lambda expressions will have distinct types. For example,
auto l1 = []{};
auto l2 = []{}; // l1 and l2 are of different types
Your lambda expression is a C++14 generic lambda, and will be translated by the compiler to a class that resembles the following:
struct __unique_name
{
template<typename Arg1, typename Arg2>
auto operator()(Arg1 self, Arg2 n) const
{
// body of your lambda
}
};
I cannot comment on the Haskell part, but the reason the recursive expression works in C++ is because you're simply passing a copy of the closure object instance (fac) in each call. The operator() being a template is able to deduce the type of the lambda even though it is not one you can name otherwise.

When should I use decltype(x) instead of auto to declare the type of a variable?

I see decltype(x) used inside macros where x is a variable name because the type of the object isn't known inside macros.
For example:
decltype(x) y = expr;
I could just have easily use auto instead of decltype. So what are those situations where decltype is needed for a variable type declaration instead of auto?
decltype becomes handy when you need to return some unknown type, which is evaluated during compilation:
template<class A, class B>
void MultiplyAB(A a, B b, decltype(a*b)& output)
{
output = a * b;
}
Additionally, if you don't like the way the output is handled by a reference, then you can also use the late-specified return type (and also use the decltype):
template<class A, class B>
auto MultiplyAB(A a, B b) -> decltype(a*b)
{
return a * b;
}
All of this, and more, is described by B. Stroustrup in the C++ FAQ.
You should use it when the required type of y is:
different (or potentially different) from the type of expr. If it was the same then auto would be more concise.
similarly for auto & or other modifications of the type of expr that auto can express.
and one of the following:
dependent on something in the surrounding code (i.e. not always the same type) and difficult to write using type traits or similar. This will tend to happen in template code. There might be a type trait that you can use to get the required type from the template parameters, but then again there might not so a use of decltype would save you defining one.
always the same type, (or dependent on template parameters in a way that is easy to express using existing type traits or similar) but the type is very long-winded to write and there is a much shorter and clear expression you can use instead.
So for example replacing std::iterator_traits<RandomAccessIterator>::value_type with decltype(*it) might well be a win, although auto does often handle such cases.
Subjective judgements enter at the point of "what is difficult", "what is long-winded" and "what is clear", but the rules of procedure can be the same regardless of how you make those judgements in specific cases.
When you want y to always have whatever the declared type of x is.
In the context of your question,
You should use decltype when you want a new variable with precisely the same type as the original variable.
You should use auto when you want to assign the value of some expression to a new variable and you want or need its type to be deduced.
decltype(x) y always declares y with precisely the same type as the type x was declared with. In particular:
If x has type const int then y will have type const int.
If x has type int[100] then y will have type int[100].
If x has type int f(int) then y will have type int f(int). Yes, this actually declares another function with the same type as the original.
If x has type int& then y will have type int&; and if x has type int&& then y will have type int&&.
auto y = x will declare y with the following types, when x has the following types:
If x has type const int, then y will have type int. That is, auto strips top-level cv-qualifiers.
If x has type int[100], then y will have type int*. That is, auto performs array to pointer conversion. [1]
If x has type int f(int), then y will have type int (*)(int). That is, auto performs function to function pointer conversion. [2]
Finally, if x has type int& or int&&, then y will have type int. That is, auto removes references.
[1] You can't use decltype here because you can't copy-initialize an array.
[2] You can't use decltype here because you can't initialize a function.
[3] The reason why auto strips references is that C++ has no expressions of reference type! Once initialized, the "reference ness" of a reference becomes invisible.
Note that decltype also does something entirely different when its argument is not an id-expression, which I won't get into here.
Whenever your variable type isn't related to the expression being evaluated.
E.g:
struct Bar
{
Bar(int) {} // implicitly constructable
}
struct Bar2
{
Bar2(int) {} // implicitly constructable
}
struct Foo
{
static Bar var;
}
struct Foo2
{
static Bar2 var;
}
template <typename T>
void dummy()
{
decltype(T::var) myVar = 42;
}
dummy<Foo>(); // myVar is of type Bar1
dummy<Foo2>(); // myVar is of type Bar2
auto myAutoVar = 42; // type is int
Of course this is just one use case, there are many more out there.
decltype is significantly more versatile that auto and can always be used in place of it. Therefore I think it's pretty safe to say that decltype should only be used in cases where it's completely necessary, so if auto produces the wrong result you should use decltype. Also you can't as of yet use auto in return types and parameters, so you can use decltype there as well. C++14 will significantly increase the potential uses of auto and I would guess c++17 will go further. So the situations to use decltype will only be when you need to change the resulting type of expr
Another thing to consider is that decltype isn't really necessary unless you're writing library code, auto is nice for everyday programming if you want to make your code more concise, it's up for debate wether using as much auto as possible is good, but it's virtually necessary when working with unutterable types like lambdas.

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);
}

How the C++0x standard defines C++ Auto multiple declarations?

mmm, I have just a little confusion about multiple auto declarations in the upcoming C++0x standard.
auto a = 10, b = 3.f , * c = new Class();
somewhere I read it is not allowed.
The reason was(?) because it was not clear if the consecutive declarations should have the same type of the first one , (int in the example) , or not.
Possible translation 1:
int a = 10;
int b = 3.f;
int * c = new Class ();
causing an error
Possible translation 2:
int a = 10;
float b = 3.f;
Class * c = new Class ();
how it is resulted in the standard?
If I can say my POV, translation #2 was the most obiouvs, at least for me that I'm a regular C++ user . I mean, for me "every variable declared is of the same declared type", witch is auto.
Translation #1 would be really un-intuitive to me.
Good Bye
QbProg
It's probably not the latest, but my C++0x draft standard from June 2008 says you can do the following:
auto x = 5; // OK: x has type int
const auto *v = &x, u = 6; // OK: v has type const int*, u has type const int
So unless something has changed from June this is (or will be) permitted in a limited form with a pretty intuitive interpretation.
The limitation is that if you do want to string multiple auto declarations like this (using the example above), it works because the inferred type of v and u have the same 'base type' (int in this case) to use an inexact term.
If you want the precise rule, The draft standard says this:
If the list of declarators contains more than one declarator, the type of each declared variable is determined as described
above. If the type deduced for the template parameter U is not the same in each deduction, the program is ill-formed.
where the "deduced template parameter U" is determined by:
the deduced type of the parameter u in the call f(expr) of the following invented function template:
`template <class U> void f(const U& u);`
Why they've come up with this rule instead of saying something like:
auto a = 10, b = 3.f , * c = new Class();
is equivalent to:
auto a = 10;
auto b = 3.f;
auto * c = new Class();
I don't know. But I don't write compilers. Probably something to do with once you've figured out the the auto keyword replaces, you can't change it in the same statement.
Take for example:
int x = 5;
CFoo * c = new CFoo();
auto a1 = x, b1 = c; // why should this be permitted if
int a2 = x, CFoo* b2 = c; // this is not?
In any case, I'm not a fan of putting multiple declarations on the same statement anyway.
The draft standard section 7.1.6.4 implies that you can't mix types like in possible translation 2. So neither possible translation is valid.
There are few points which I think help contribute to the current wording. The first is consistency. If you write a declaration such as:
int j;
int i = 0, *k = &j;
Then, the two types both use int somewhere in their type. It is an error for example, if 'j' was a 'const int'. Having auto work the same is "consistent" with the current model for such declarations.
There is then a practical advantage from a compiler writer's perspective. There already exists the machinery in compilers to perform type deduction for a function call (which is how auto is described as operating). The above rule is consistent with the rule that deduction for a template parameter must result in the same type:
template <typename T> void foo (T, T*);
void bar ()
{
int i;
const int j = 0;
foo (i, &i); // OK deduction succeeds T == int
foo (i, &j); // ERROR deduction fails T == int or const int.
}
An easy implementation for auto is to reuse the existing mechanics to deduce the type for auto, and because of the consistency with current behaviors the result will not be too surprising to people.
Finally, and this is probably the most important point in my opinion. The current model is conservative and works very similarly to existing language constructs in C++. Therefore the chances of it resulting in a show stopping language bug are very slim. However, the current model can easily be expanded in the future to contain the other examples you list in your question.
What's more, that expansion will not break code that uses the current wording of auto. Consider if they did this the other way round. If the expanded version of auto was the first version, and this resulted in some weird and fatal flaw with the keyword then it would require the removal of a language feature from the standard - something which almost never happens as it most likely breaks code.
The first one would kind of line up with how type inference works in the return type of the ?: operator.