GotW #94 Special Edition: AAA Style (Almost Always Auto)

Toward correct-by-default, efficient-by-default, and pitfall-free-by-default variable declarations, using “AAA style”… where “triple-A” is both a mnemonic and an evaluation of its value.

 

Problem

JG Questions

1. What does this code do? What would be a good name for some_function?

template<class Container, class Value>
void some_function( Container& c, const Value& v ) {
if( find(begin(c), end(c), v) == end(c) )
c.emplace_back(v);
assert( !c.empty() );
}

2. What does “write code against interfaces, not implementations” mean, and why is it generally beneficial?

Guru Questions

3. What are some popular concerns about using auto to declare variables? Are they valid? Discuss.

4. When declaring a new local variable x, what advantages are there to declaring it using auto and one of the two following syntaxes:

(a) auto x = init; when you don’t need to commit to a specific type? (Note: The expression init might include calling a helper that performs partial type adjustment, such as as_signed, while still not committing to a specific type.)

(b) auto x = type{ init }; when you do want to commit to a specific type by naming a type?

List as many as you can. (Hint: Look back to GotW #93.)

5. Explain how using the style suggested in #4 is consistent with, or actively leverages, the following other C++ features:

(a) Heap allocation syntax.

(b) Literal suffixes, including user-defined literal operators.

(c) Named lambda syntax.

(d) Function declarations.

(e) Template alias declarations.

6. Are there any cases where it is not possible to use the style in #4 to declare all local variables?

29 thoughts on “GotW #94 Special Edition: AAA Style (Almost Always Auto)

  1. @jlehrer
    g++ 4.7.3 says

    auto i = 0, j = 1;   //does compile
    auto i = 0, *j = &i; //does compile
    auto i = 0, j = 2.0; //error: inconsistent deduction for 'auto': 'int' and then 'double'
    
  2. So this is well formed:

    auto i=1,j=2;

    but this is ill-formed:

    auto i=1,j=2.0;

    Okay.

    What about this:

    auto x=1,*j=&x;

    I’m going to guess that is ill-formed as well.

  3. @jlehrer: That’s ill-formed, both have to deduce to the same type. I’m not sure but as I remember GCC does allow it, and gives you an int and a float, but that’s nonconforming behaviour.

  4. I don’t have an 11 compiler to play with right now or I’d answer this question myself.

    What does this do:

    auto i=1,f=2.0;

    Does that make two integers, an int and a float, or two floats?

  5. > While copy/move elision can probably optimize it, MSVC in particular is still bad in it, and misses quite a few cases

    Herb’s secret agenda: Teach programmers to use code MSVC is bad at (unsupported C++11 and 14 features, stuff that triggers bugs) to force the compiler team to fix everything.

    Hey Herb, can you write an article about how our source files should be UTF-8-encoded without a BOM, and we should use std::codecvt_utf8 to convert from our narrow string literals to wide strings?

  6. @Johan: Good point. All the more reason to use -Wfloat-equal.

    Apart from beeing explicit about conversions and symmetry I find the AAA style more pleasant to read. Variable names are always at the beginning of a line:

    auto a = type_name{args};
    auto b = long_type_name{args};
    auto ccc = even_longer_type_name::subtype_name{args};
    

    vs.

    type_name a {args};
    long_type_name bb{args};
    even_longer_type_name::subtype_name ccc{args};
    
  7. 3. When a function is modified to return a different data type, it may silently cause problems if auto was used. For example, if some return types are changed from integer to float or double:

    auto a = c.GetValue();
    auto b = d.GetValue();
    
    if( a == b )
    {
      // Comparing integers are fine, but should not compare float numbers like this.
    }
    

    Unit tests might detect this, but anyway you need to be extra careful when using auto.

    Declaring a and b as integers should at least cause the compiler to show a warning about the conversion to int.

    Also, sometimes readability can suffer because you need to stop and think what the type is, instead of just reading the explicit type. However, when working with the standard library this is not an issue since most programmers knows it well.

  8. @Jeff Harris:
    auto always gives you a reference/cv-stripped type. To keep them, you need to use decltype instead. The thing in C++11 is that you have to tell decltype what to deduce the type from:

    decltype(getWidget()) w = getWidget(); //w has type const Widget& 

    C++14 gives you decltype(auto), and does exactly what you want here:

    decltype(auto) w = getWidget(); //w has type const Widget& 
  9. @Bret Kuhns
    Yes, the examples are symmetric, but not necessarily in the ‘spirit’ of auto. The guideline in GotW 93 for using auto simply states to use auto w = getWidget() and say the compiler will choose the correct type. For a getWidget returning a reference, the type likely is not correct as it will either not compile (if type is not copyable) or be slow with a potentially unwanted copy (not move). Shouldn’t there be more qualifications on the guidelines that they hold for value returning expressions only? If a reference is returned, then auto& or const auto& is required.

  10. @John That is right. It was subject of a recent SO discussion. The solution/workaround for the case where the wished type is an array is to use a reference, which prevents automatic decay

     auto &&x = identity <int []>{a, b, c}; 

    But honestly, I am going to continue writing “type var{init}”.

  11. @Jeff Harris, you first show

    auto w = getWidget();

    Then note that it is different than

    const Widget& w = getWidget();

    Notice the asymmetry in these examples? Now how do we use `auto` to match the second line?

    const auto& w = getWidget();

    Pretty symmetric. We control when we get a copy, or when we get a reference to const, and it’s all documented at the declaration site.

  12. This isn’t compiling for me:

    auto buffer = char[10]{};
    

    If it could compile, I think buffer would be a char*, not char[10].
    Right?

  13. Herb,
    Thanks for answering quickly.

    Looking forward to watching your talk at build!

  14. 1. What’s the point of using emplace_back() if v is not an rvalue reference?

  15. @Herb: Oh, since you mention rise4fun, I have a related question real quick /* if you’re not the right guy to ask, apologies in advance; perhaps you could pass it on? :-) */.

    Is there any chance to extend it to work like the other online compilers linked to from http://isocpp.org/get-started — in particular Stacked-Crooked or ideone.com — i.e., to run the program and show the output?

    What’s really nice about Stacked-Crooked in particular is that it also supports Boost, which allows to quickly test (and share) code snippets such as this:
    http://coliru.stacked-crooked.com/view?id=4af2bf3c771a9413c642dd8e26007df8-3725be9f9ce62f113fc473b4ae69c419

    Regardless, another issue is the following:
    “testvc.cpp(1) : info : Ignoring directive ‘#include’, which cannot be used in this online version of the Visual C++ compiler”

    This is a problem for verifying the correctness of the code at hand — for instance, std::size_t ordinarily requires an inclusion of a header like cstddef (there are others), and rise4fun automatically including all the standard headers (which seems to be implied by the support for library constructs such as std::vector) currently leaves simple mistakes like this undiagnosed (I’d also imagine this leads to a heavier strain on the servers supporting rise4fun, so if anything, fixing this should be a win-win).

    Pretty please? :-)

  16. 3: With a standard ‘getter’ function, e.g. const Widget& getWidget(), the use of auto w = getWidget() will deduce a type of Widget for w. The code will attempt to create a copy of Widget which may be slow or not allowed if Widget is not copyable. Traditionally, you’d declare const Widget& w = getWidget() which avoids any copies and is fast.

    Is there a version of auto that will attempt to preserve the cv and reference qualifications on the deduced type vs. following the template rules of the current auto? The use of auto& works for the getWidget() case but fails if getWidget returns a Widget by value and not const reference. const auto& is required in that case.

    It seems as if the caller still needs to be as aware of the exact return type of getWidget when using auto as when not using auto.

  17. Herb,

    Microsoft, and you in particular, has done a tremendous job of supporting the c++ community.
    Still, I am absolutely shocked at the abyssimal support of c++11 in visual studio, it not even supporting initializer lists such as int i { 4 }; (please tell me I’m wrong and some configuration enables 11 support).

    Nowadays clang and gcc++ have next to 100% c++ 11 support and beyond.

    When can we expect a better support in visual c++?

  18. 4b: Pleeeeheheheeeeeaaase don’t.

    If you want a given type, declare the variable with that type. Don’t declare a type-deduced variable initialized by a value converted to the given type, it’s just dumb.

    I know you want to suggest that it is self-documenting that there is a conversion. But it equally documents this if you use type x = init; only when you want conversion, and auto x = init; if you don’t. Or if you really-really want to document it, use type x = type{init}; but that’s redundant.

    Using auto x = type{init}; also might involve a move-construction. While copy/move elision can probably optimize it, MSVC in particular is still bad in it, and misses quite a few cases (in the range of 5-10% – not lot, but nothing guarantees this 5% won’t be in your performance-critical code). Also, in debug mode you don’t have copy elision at all, resulting in different behaviour than release mode – noone wants that.

    This way feels like Maslow’s hammer: we now have a super new tool since C++11, auto. It is awesome and stuff, but we still don’t need to use everywhere. It should raise the red flag if you need to write something incredibly verbose, that you should not use it after all.

  19. @Adrian: I had the assertion outside in my original code, and I agree it’s better there. Lightly edited the question to do that.

  20. 6) There will be a problem with “long long{init}”. If “type” consists of multiple parts like that, “long long x{init}” must be used, or the type name needs to be aliased such as “auto x = std::common_type_t{init}”.

  21. In #1, the assertion would be even stronger to put it at the end of the function rather than inside the then clause. That way it becomes a true post condition–the container must contain something if it contains a value equal to v.

    This post condition also works as a form of documentation. A stronger post condition would be to assert that the container contains the value:

    template <typename Container, typename Value>
    bool Contains(const Container & c, const Value & v) {
        using std::begin;
        using std::end;
        return std::find(begin(c), end(c), v) != end(c);
    }
    
    template <typename Container, typename Value>
    void AppendIfUnique(Container & c, const Value & v) {
        if (!Contains(c, v)) c.emplace_back(v); 
        assert(Contains(c, v));  // Post condition (barring exceptions)
    }
    
  22. 1. It searches c for an object that compares equal to v and if it does not find one it constructs an element in place at the end of c using v.
    2. It means to write code against intentional, public, specified guarantees. It’s beneficial because it means implementations can improve independently as long as the interface is maintained; bugs can be fixed, features can be added, all without unnecessary cascading code changes. Secondly because writing code that just seems to work but isn’t intentionally well specified can result in errors even without changes in the implementation, if the implementation isn’t well understood.
    3. That the type won’t be statically specified; no, auto in C++ uses static type deduction. That the type will be hidden, making it difficult to understand code; possibly because interfaces might be specified in terms of specific types and a source editor might lack support for showing correctly deduced types.
    4. a) The type of init can change without necessarily requiring the code using it to change in order to operate correctly. The possibility for an erroneous, silent implicit conversion is avoided. The initializer cannot be mistakenly omitted.
    b) I don’t see any particular advantage to auto x = type{init}; vs. type x {init};.
    5. a) Heap allocation syntax already explicity specifies types. Using auto x = make_unique(init); avoids duplication.
    b) Literal suffixes also specify type and auto again avoids repeating it.
    c) Lambda types are un-utterable and type deduction is required to get a named lambda.
    d) formal parameters can have their types deduced from default values.
    e) Type alias syntax using X = type;
    6. auto declarations require the use of ‘inside-out’ syntax in order to add cv-qualifiers or to obtain reference types. You cannot use left-to-right type syntax, e.g. template using rref = T&&; rref x = f();. You must write: auto &&x = f();, or use explicit types: rref x = f(); or more verbose syntax using decltype.

  23. Expression templates are one thing you can’t use auto with, unless the proposed ‘operator auto()’ to suggest the type to convert to actually went somewhere.

  24. one of the most annoying thin in c# is :
    var variable = SomeFunctionCall();

    now you have to go hunt in the declaration of the function what type does it return :(

    quite disheartened you recommend go all auto :(

  25. 3. My only concern on using auto is my IDE doesn’t autocomplete through such variables, making it annoying enough to not use half the time.

Comments are closed.