auto x = ..., y = ...


  • Mod

    Bin gerade zu faul zu nachschlagen, vielleicht hat ja jemand die Antwort parat:

    Wieso muss bei Mehrfachdefinition mit auto jeweils der gleiche Typ deduziert werden, d.h. warum existiert diese Regel?

    int main() {
        auto x = 1, y = '0';
    }
    

    https://godbolt.org/g/mhKQHH

    Ist ein bisschen unpraktisch, wenn man mehrere Variablen in Kontrollstrukturen (for, if) definieren möchte.
    Man kann es mit structured bindings teilweise umgehen:

    #include <tuple>
    
    int main() {
        auto [ x, y ] = std::tuple( 1, '0' );
    }
    

    Allerdings ist das aus meiner Sicht umständlich und die Lesbarkeit unnötig erschwert, und man kann nicht Objekte und Referenzen mischen.



  • Ich kann Dir nicht sagen warum, aber es steht in §10.1.7.7:

    If the init-declarator-list contains more than one init-declarator, they shall all form declarations of variables. The type of each declared variable is determined by placeholder type deduction (10.1.7.4.1), and if the type that replaces the placeholder type is not the same in each deduction, the program is ill-formed.
    [Example:
    auto x = 5, *y = &x; // OK: auto is int
    auto a = 5, b = { 1, 2 }; // error: different types for auto
    — end example]


  • Mod

    Ich kann am Montag einfach im UK Panel nachfragen, aber der Grund wird wohl Einheitlichkeit sein. In einer gewöhnlichen init-declarator-list sind die deklarierten Bezeichner ja auch alle gleichen Typs. Das Schlüsselwort soll indes als ein fast schon syntaktischer Platzhalter für einen Typen angesehen werden.

    Ist ein bisschen unpraktisch, wenn man mehrere Variablen in Kontrollstrukturen (for, if) definieren möchte.

    Wie meinen?


  • Mod

    Arcoth schrieb:

    Ist ein bisschen unpraktisch, wenn man mehrere Variablen in Kontrollstrukturen (for, if) definieren möchte.

    Wie meinen?

    Z.B.

    for ( auto p = get_pointer(), i = 0; i != len; ++i ) ...
    

    um dann etwa in der Schleife auf p[i] zugreifen zu können.

    for ( auto [p, i] = std::tuple(get_pointer(), 0); i != len; ++i )
    

    ... sieht nicht so toll aus.

    Also hat man am Ende doch wieder mit

    auto p = get_pointer();
    for ( int i = 0; i != len; ++i )
    

    den umgebenden Scope mit einem Bezeichner verseucht, der dort nicht gebraucht wird.


  • Mod

    Also hat man am Ende doch wieder mit

    auto p = get_pointer();
    for ( int i = 0; i != len; ++i )
    

    den umgebenden Scope mit einem Bezeichner verseucht, der dort nicht gebraucht wird.

    Das mache ich immer so, und nehme es nicht als Problem wahr. Wie groß sind bitte deine Scopes?


  • Mod

    Arcoth schrieb:

    Also hat man am Ende doch wieder mit

    auto p = get_pointer();
    for ( int i = 0; i != len; ++i )
    

    den umgebenden Scope mit einem Bezeichner verseucht, der dort nicht gebraucht wird.

    Das mache ich immer so, und nehme es nicht als Problem wahr. Wie groß sind bitte deine Scopes?

    Wenn ich eine solche Schleife habe, habe ich gelegentlich gleich noch eine weitere.
    Wenn ich Glück? habe, kann ich p wiederverwenden - nur ist dann die Deklaration weit weg. Was bedeutet, dass ich einen vernünftigen Namen (statt eines Platzhalters) finden muss (wenn alles beisammen steht, und der Code kompakt ist, ergibt sich die Bedeutung eines Bezeichners ja unmittelbar aus der Struktur des Codes).
    Wenn ich p nicht wiederverwenden kann, muss ich mir einen andern Bezeichner suchen, meinetwegen q. Das ist seine eigene Weise auch nicht hübsch: Wenn die Schleife isoliert betrachtet wird, ist nicht klar, wieso nicht die natürliche Wahl p getroffen wurde. Wieder kann man den Code dann nur verstehen, indem man den gesamten Scope betrachtet.
    Alternativ nimmt man richtige Namen (statt reinen Platzhaltern), dass ist dann wieder zusätzliche Schreibarbeit, und führt ggf. zu weniger kompakten Code.


  • Mod

    #include <tuple>
    
    template <typename Iterator>
    class indexed_iter_range {
    public:
      using difference_type = typename std::iterator_traits<Iterator>::difference_type;
    
    private:
      Iterator iter;
      difference_type len;
    
    public:
      constexpr indexed_iter_range(Iterator iter, difference_type len) : iter(iter), len(len) {}
    
      struct iterator {
        private:
          Iterator p;
          difference_type i = 0;
          friend indexed_iter_range;
    
          constexpr iterator(Iterator p) : p(p) {}
    
        public:
          constexpr auto operator*() {
            return std::tuple{p, i};
          }
          constexpr iterator& operator++() {
            ++p;
            ++i;
            return *this;
          }
          constexpr bool operator!=(iterator i) {
            return p != i.p;
          }
      };
    
      constexpr iterator begin() {return iter;}
      constexpr iterator end() {return iter + len;}
    };
    
    #include <iostream>
    int main() {
      int arr[] {1, 2, 3, 4};
      auto len = std::size(arr);
    
      for (auto [p, i] : indexed_iter_range(arr, len))
        std::cout << i << ": " << *p << '\n';
    }
    

    🕶



  • Dein operator++ ist merkwürdig. Mir scheint, du hast hier pre/postincrement irgendwie kombiniert 😮


  • Mod

    Quod licet Iovi...


  • Mod

    Tatsächlich fällt mir gerade auf, dass wir hier eine sehr nette Verallgemeinerung treffen können: die Range kann eine Reihe von Objekten speichern, die in jedem Schritt inkrementiert werden.

    #include <tuple>
    
    template <typename Range, typename... Auxs>
    class incrementing_range {
      Range range;
      std::tuple<Auxs...> auxs;
    
    public:
      constexpr incrementing_range(Range&& r, Auxs&&... args)
        : range(std::forward<Range>(r)),  auxs(std::forward<Auxs>(args)...) {}
    
      template <typename Iter>
      struct sentinel;
    
      template <typename Iter>
      struct iterator {
        Iter p;
        std::tuple<Auxs...> auxs;
    
        constexpr iterator(Iter p, std::tuple<Auxs...> const& auxs) : p(p), auxs(auxs) {}
    
        constexpr auto operator*() {
          return std::tuple_cat(std::tuple{p}, auxs);
        }
        constexpr iterator& operator++() {
          ++p;
          std::apply([](auto& ...x){(++x, ...);} , auxs);
          return *this;
        }
        constexpr bool operator!=(sentinel<Iter> i) {
          return p != i.p;
        }
      };
    
      template <typename Iter>
      struct sentinel {
          Iter p;
      };
    
      friend constexpr auto begin(incrementing_range& r) {
        using std::begin;
        return iterator<decltype(begin(r.range))>{begin(r.range), r.auxs};
      }
      friend constexpr auto end(incrementing_range& r) {
        using std::end;
        return sentinel<decltype(end(r.range))>{end(r.range)};
      }
    };
    
    template <typename Range, typename... Auxs>
    incrementing_range(Range&&, Auxs&&...) -> incrementing_range<Range, Auxs...>;
    
    #include <iostream>
    int main() {
      int arr[] {1, 2, 3, 4};
    
      for (auto [p, i] : incrementing_range(arr, 0))
        std::cout << i << ": " << *p << '\n';
    }
    


  • Man kann auch einfach nen Scope drumrum machen um die Lifetime und Visibility von p zu begrenzen. Also wenns einfach nur darum geht das Problem zu lösen.


  • Mod

    hustbaer schrieb:

    Man kann auch einfach nen Scope drumrum machen um die Lifetime und Visibility von p zu begrenzen. Also wenns einfach nur darum geht das Problem zu lösen.

    Aber das Problem ist doch ästhetischer Natur: wir möchten den Code prägnant halten. Daher ja die kurzen Variablennamen. Um mehrere Schleifen geschweifte Klammern zu setzen, fände ich fast schon prekär, so etwas sieht man ja auch in der Praxis so gut wie nie. Da würde ich meine Lösung präferieren.



  • Arcoth schrieb:

    Aber das Problem ist doch ästhetischer Natur: wir möchten den Code prägnant halten.

    Ich glaube dass ein zusätzlicher Scope für fast jeden angenehmer zu lesen ist als der eigenartige einer ihm unbekannten Hilfsfunktion.
    Kommt natürlich drauf an wie oft man ein Konstrukt in einem Projekt antrifft. Wenn es ein paar wenige male vorkommt ziehe ich einen zusätzlichen Scope auf jeden Fall vor. Wenn es hunderte male vorkommt ist es vermutlich OK.

    Arcoth schrieb:

    Um mehrere Schleifen geschweifte Klammern zu setzen, fände ich fast schon prekär, so etwas sieht man ja auch in der Praxis so gut wie nie. Da würde ich meine Lösung präferieren.

    Das sieht man in der Praxis dauernd. Wobei ich es vorziehe aussagekräftige Variablennamen zu verwenden sobald mehrere Dinge in der selben Funktion vorkommen die einen Namen wie "p" haben könnten. Damit wird das ganze dann auch automatisch kollisionsfrei.


Anmelden zum Antworten