53
votes

Does C++11 allow declaring non-static data members as 'auto' if they are initialized in the declaration? For example:

struct S
{
    auto x = 5;  // in place of 'int x = 5;', which is definitely allowed
};

GCC 4.7 rejects the above code, while it accepts int x = 5;.

Assuming this is not a compiler bug but rather the standard really doesn't allow it, why not? It would be just as useful as declaring local variables auto.

2
It is allowed to declare a static data member this way, but not a non-static data member (see the list of allowed auto uses in C++11 §7.1.6.4). Off the top of my head, I can't think of a good reason why it is prohibited, though I would definitely consider this to be an abuse of auto.James McNellis
@JamesMcNellis: Why would you consider this an abuse of auto? What is the difference between having a variable with a long/complicated type name (so that it is desirable to use auto rather than write out its type) at class scope vs. having one at local scope?HighCommander4
At block scope I know how the variable is being used. At class scope or namespace scope, I do not necessarily know how the variable is being used. Consider, for example, the more general auto x = f(a, b, c);: I'd have to go find all of the f() functions and perform overload resolution in my head to figure out the type of x. At least at local scope I can look to see what is being done with x and try to deduce from that what its type is. auto is insanely useful, but it shouldn't be used everywhere.James McNellis
@JamesMcNellis: Interestingly, auto is allowed at namespace scope. I don't see why class scope is singled out like that...HighCommander4
See this ISOCPP mailing list discussion about a prototype implementation of non-static auto members, and also the committee's discussion thereof recorded in N3897 Auto-type members.Casey

2 Answers

67
votes

The rule for prohibiting non-static members is in 7.1.6.4 clause 4:

The auto type-specifier can also be used in declaring a variable in the condition of a selection statement (6.4) or an iteration statement (6.5), in the type-specifier-seq in the new-type-id or type-id of a new-expression (5.3.4), in a for-range-declaration, and in declaring a static data member with a brace-or-equal-initializer that appears within the member-specification of a class definition (9.4.2).

I found the rationale for it being static here which reflects how James McNellis explains it in the comment.

One national body dislikes allowing the auto type-specifier for non-statics. From an e-mail to the authors:

    template< class T >
    struct MyType : T {
      auto data = func();
      static const size_t erm = sizeof(data);
    };

In order to determine the layout of X, we now have 2-phase name lookup and ADL. Note that func could be either a type or a function; it may be found in T, the namespace of MyType, the associated namespace(s) of T when instantiated, the global namespace, an anonymous namespace, or any namespaces subject to a using directive. With care we could probably throw some concept_map lookup in for luck. Depending on the order of header inclusion I might even get different results for ADL, and break the One Definition Rule - which is not required to be diagnosed.

Because of this controversy, the authors no longer propose that auto be allowed for non-static data members.

So, basically depending on the order of header inclusion, the type of data could be very different. Of course, auto x = 5; would not need to depend on 2-phase name lookup or ADL, however, I'm a assuming that they made it a "blanket" rule because otherwise, they would have to make individual rules for every use case which would make things very complicated.

In the same paper, the author proposes eliminating this restriction, however, it seems this proposal has been rejected probably due to the above rationale and also so that expected behavior can be the same no matter what the initializer is.

4
votes

For others:

Using C++17 this is indirectly (automatic deduction of non-static member type) possible. You need to use templates and deduction guides to make it happen:

template< class data_t>
struct MyType 
{
    data_t data;
    static constexpr auto data_size = sizeof(data_t);

    MyType( data_t && p_data ) : data(p_data) {}
};

template< class data_t>
MyType(data_t &&) -> MyType<std::remove_reference_t<data_t>>;

I don't know how but this auto members really need to make it into the language without them certain patterns are next to impossible.

The scenario above does not work if the lambda captures a member of class by reference. This is a useful pattern for, highly compensable classes that avoid the use of type erased functions. Something I regularly do on embedded systems.

https://godbolt.org/z/W-K9Uk

You can mangle the language into submission allowing a lambda to reference a member of a non-existent class using placement-new and offset_of but that is ludicrous and shouldn't be required.