ostream operator<< für String-like Container

german

Aktives Mitglied
devCommunity-Experte
Keine Frage, aber auch nicht Wert für einen Beitrag in den Ressourcen ...

Bin heute über einen alten Code aus C++11 Zeiten gestolpert, in dem ich operator<< für std::array und std::vector überladen habe, sofern aus char-Typen bestehend. Ziemlich viel Code für so eine simple Aufgabe.
Als ich die ersten beiden Variablen gesehen habe ist mir sofort aufgefallen - sieht verdächtig nach string_view aus. Und - wie nicht anders zu erwarten - lässt sich der Code damit auf eine Zeile eindampfen. Natürlich wird der alte Code so oder ähnlich immer noch ausgeführt. Allerdings ist er in der Implementierung für die Überladung des string_streams verschwunden.

Hier mal der Code als Anschauungsmaterial und, falls jemand Lust drauf hat, zum Zerreißen und Fragen stellen.
C++:
#include <iostream>
#include <iomanip>
#include <type_traits>
#include <array>
#include <vector>

#include <string_view>

/*
template<typename classT, typename charT, typename traitsT = std::char_traits<charT>>
std::basic_ostream<charT, traitsT>& operator<<(std::basic_ostream<charT, traitsT>& ostr, const classT& string_like)
{
  // `string_like` has to have a `data()` method and its return type must be
  // brace-initializable to a pointer to const char_type of the ostream
  const charT* const str{ string_like.data() };
  // `string_like` has to have a `size()` method
  // its return type might get narrowed (because streamsize is a signed type)
  const std::streamsize length(string_like.size());

  if (typename std::basic_ostream<charT>::sentry{ ostr })
  {
    try
    {
      const auto ostr_width{ ostr.width() };
      if (ostr_width > length)
      {
        const auto left_is_set{ (ostr.flags() & std::basic_ostream<charT>::adjustfield) == std::basic_ostream<charT>::left };
        if (!left_is_set)
        {
          for (auto pad{ ostr_width - length }; pad-- > 0; )
          {
            if (ostr.rdbuf()->sputc(ostr.fill()) == traitsT::eof())
            {
              ostr.setstate(std::basic_ios<charT, traitsT>::badbit);
              break;
            }
          }
        }

        if (ostr.good() && ostr.rdbuf()->sputn(str, length) != length)
          ostr.setstate(std::basic_ios<charT, traitsT>::badbit);

        if (left_is_set && ostr.good())
        {
          for (auto pad{ ostr_width - length }; pad-- > 0; )
          {
            if (ostr.rdbuf()->sputc(ostr.fill()) == traitsT::eof())
            {
              ostr.setstate(std::basic_ios<charT, traitsT>::badbit);
              break;
            }
          }
        }
      }
      else if (ostr.rdbuf()->sputn(str, length) != length)
        ostr.setstate(std::basic_ios<charT, traitsT>::badbit);

      ostr.width(0);
    }
    catch (...)
    {
      ostr.setstate(std::basic_ios<charT, traitsT>::badbit);
    }
  }
  else
    ostr.setstate(std::basic_ios<charT, traitsT>::badbit);

  return ostr;
}
*/

template<typename classT, typename charT, typename traitsT = std::char_traits<charT>>
std::basic_ostream<charT, traitsT>& operator<<(std::basic_ostream<charT, traitsT>& ostr, const classT& string_like)
{
  return operator<<(ostr, std::basic_string_view<charT, traitsT>{ string_like.data(), string_like.size() });
}

int main()
{
  const std::array arr{ 't', 'e', 's', 't' };
  const std::vector vec{ 'c', 'o', 'd', 'e' };
  std::cout << arr << std::endl;
  std::cout << vec << std::endl;
  std::cout << std::setw(10) << arr << std::endl;
  std::cout << std::setw(10) << vec << std::endl;
  std::cout << std::setw(10) << std::left << arr << "foo" << std::endl;
  std::cout << std::setw(10) << std::left << vec << "foo" << std::endl;
}

// Edit: der zweite Parameter ist eine Referenz und keine Kopie :oops:
 
Zuletzt bearbeitet:
Oh, ich sehe gerade einen Lapsus :eek: Hab den Funktionskopf einfach übernommen ohne noch mal zu prüfen ...
const classT& string_like wäre hier angebracht gewesen.
 
Naja, als Autodidakt mit Null Vorahnung von kompilierten Sprachen kannst du dir vorstellen mit was ich angefangen habe. "C+-" mit WinAPI im bunten Mix. Irgendwann bin ich drauf gekommen, dass es einen Unterschied zwischen C und C++ gibt und dass die WinAPI doch eher C-Style ist. Also hab aus dem C+- ein C gemacht und C++ erst mal gestrichen. Vor ein paar Jahren hab ich dann allerdings mit -AB- an seinem plangDetect Projekt gebastelt und mich somit nebenher auch wieder mit C++ beschäftigt. Und nachdem ich im vorigen Jahr angefangen habe, die Redmonder Jugendbrigade zu unterstützen, die sich mit dem Windows Terminal beschäftigen, hab ich auch wieder mit C++ zu tun (und mit den ganzen Microsoft-spezifischen Libs).

Die Operatorüberladung hab ich übrigens seinerzeit zum Debuggen geschrieben, wenn ich mit C++ und WinAPI gearbeitet habe. Die Stringfunktionen der WinAPI erwarten natürlich ein Pufferarray. Die data() Methode eines std::string liefert aber vor C++17 nur einen Pointer auf const. Somit für mich nicht zu gebrauchen. Bei einem std::vector ist das aber von jeher ein Pointer auf den internen Puffer gewesen, der veränderbar ist. Somit war ein vector eine ziemlich gute Alternative zu per Hand allokierten Pointern...

Wie auch immer, heute würde ich dafür nicht mehr den << Operator überladen. Denn das hatte immer den Nachteil dass der vector vorab auf die tatsächlich beschriebene Größe gebracht werden musste. Sinnvoller ist vermutlich eine Convenience-Funktion, die ein string_view zurückgibt. Die kann man auch gleich so überladen, dass sie ein view auf den benötigten Substring zurückgibt. Etwa so:
C++:
#include <iostream>
#include <type_traits>
#include <string_view>
#include <string>
#include <array>
#include <vector>

template<typename classT, typename charT = typename classT::value_type, typename traitsT = typename std::char_traits<charT>>
constexpr auto make_sv(const classT& string_like) noexcept
{
  return std::basic_string_view<charT, traitsT>{ string_like.data(), string_like.size() };
}

template<typename classT, typename charT = typename classT::value_type, typename traitsT = typename std::char_traits<charT>>
constexpr auto make_sv(const classT& string_like, const size_t pos, const size_t count = std::basic_string_view<charT, traitsT>::npos)
{
  return make_sv(string_like).substr(pos, count);
}

int main()
{
  const std::string str{ "test" };
  std::cout << make_sv(str, 2) << std::endl;
  const std::array arr{ 't', 'e', 's', 't' };
  std::cout << make_sv(arr, 1, 2) << std::endl;
  const std::vector vec{ 't', 'e', 's', 't' };
  std::cout << make_sv(vec) << std::endl;
}
// Edit: noexcept für die erste Überladung
// Edit2: verkürzte Version für die zweite Überladung
 
Zuletzt bearbeitet:
Wusste garnicht, dass du sogar ein Projekt mit -AB- gemacht hast. Deer würde dem Forum hier auch gut tun :(

Ich hatte immer angenommen du bist fast reiner C-Programmierer der nur hier und da mal was mit C++ zutun hat :)
 
Wusste garnicht, dass du sogar ein Projekt mit -AB- gemacht hast.
Nicht viel. plangDetect war gedacht um vielleicht sogar im Forum mal 15 Zeilen Code relativ sicher einer Programmiersprache zuordnen zu können und dann mglw. automatisch richtig zu highlighten. So wirklich gut hatte das aber noch nicht funktioniert :D
Deer würde dem Forum hier auch gut tun :(
Ja, da sagst du was :(
von "hier und da was mit C++ zu tun haben" ist sowas hier weit entfernt
Nee, nicht wirklich. Die Herausforderung war anfangs eher die präferierten Libs zu verwenden, incl. mit TAEF Unit-Tests zu schreiben (Ochs und Scheunentor etc...). Wie oben geschrieben, bin ich da vor nicht mal 10 Jaren mit null Vorahnung rangegangen. C++ vielleicht 5 Jahre. Das ist noch nicht viel mehr als "hier und da". Mach das ja nur aus Spaß und nicht beruflich ;)
Kannste dir ja ausmahlen wie gut er in C sein muss oder ? ;)
Nun is aber gut :ROFLMAO:
 
Jein. Also, es ist definitiv nicht die cmd.exe. Die greift niemand mehr an. Offiziell "because we would break the world" wegen all der Bugs, die seit Jahren aber bekannt und somit sogar genutzt werden. Inoffiziell will man da einfach keine Energie mehr reinstecken und die User dazu bewegen sich auf die PowerShell zu stürzen oder alternativ die Linux Tools über WSL zu nutzen.

Hier geht es um das Consolefenster (conhost.exe) und das neue Windows Terminal (wt.exe). Letzteres steht kurz vor Version 1.0 und somit vor der offiziellen Auslieferung vermutlich mit dem Frühjahrsupdate von Win10. Beide teilen sich einen Teil des Codes (den oben verlinkten inbegriffen, der dann auch bspw. in der WriteConsoleA API Funktion aktiv ist).
 
Wow und da schreibst du einfach mal mit dran rum? Respekt. Hätte nie gedacht, dass der german aus unserem kleinen Forum hier an Projekten von Microsoft schreibt :)
Da komm ich garnicht drüber hinweg. Hätte gedacht das machen direkt Mitarbeiter von Microsoft am anderen Ende der Welt. Und dann ist es jemand, der einem schon vor drei Jahren Tipps gegeben hat warum das erste HelloWorld in Code::Blocks nicht kompiliert XD

Nunja, da weiß ich wem ich künftig die Schuld in die Schuhe schieben kann wenn etwas nicht funktioniert ;)
 
Wow und da schreibst du einfach mal mit dran rum?
Kannst du auch. Ist open source.
da weiß ich wem ich künftig die Schuld in die Schuhe schieben kann
Wenn was nicht funktioniert, machst du dort ein Issue auf. Wenn's meinen Code betrifft, werde ich automatisch hellhörig. Wäre nicht das erste mal. Bugs einbauen kann ich genauso gut wie die Jungs aus Redmond :p
 
Zurück
Oben Unten