Autor Wątek: Format string  (Przeczytany 5106 razy)

Offline Xender

  • Użytkownik

# Maj 12, 2015, 15:16:40
Jakie znacie fajne implementacje format stringów?
Szczególnie dla C++ (aczkolwiek nie tylko), jemu brakuje dobrego I/O w bibliotece standardowej (printf to C i w C++ da się to zrobić bezpieczniej/wygodniej, iostream jest niewygodne).

Powiem szczerze, że cout/cin to ja chyba jeszcze nigdy nie użyłem. :) Zawsze był printf, a od dłuższego już czasu doszedł mi własny wrapper na vsnprintf w postaci format(fmt,...). :) Z coutem zawsze jest za dużo roboty żeby coś sformatowac dokładnie tak jak się chce, a scanf/cin do żadnego poważniejszego parsowania wejścia jak dla mnie się nie nadają (w sumie podobnie jak całe standardowe wejście - chyba że pisze się commandlinowe toole konkretnie do przetwarzania tekstu). :)

Bardzo z kolei podobają mi się Pythonowe format stringi (te nowe, ze {}, nie te z %) - nawet znalazłem port do C++:
https://github.com/cppformat/cppformat
Używał ktoś tego lub czegoś podobnego i może podzielić się wrażeniami?

Szkoda, że na Pythonowych format stringach ciężej zrobić wejście chociaż w sumie jest moduł i od tego, parse.

Pozdrawiam. ^ ^
« Ostatnia zmiana: Maj 12, 2015, 15:21:18 wysłana przez Xender »

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Kos

  • Użytkownik
    • kos.gd

  • +3
# Maj 12, 2015, 15:34:02
Ja mam fajny przykład, ale dla format stringów dla daty i czasu. Wiadomo że flag strftime / strptime nie da się spamiętać (trzeba wytatuować na przedramieniu albo zapisać permanentnym cienkopisem na wewnętrznej stronie okularów).

Ucieszyłem się widząc że ludzie od Go nie poszli w tę stronę. U nich date format string wygląda np: "Mon, 02 Jan 2006" albo "2006-01-02". I działa!
Zasada działania jest prosta: bierzesz konkretną datę (unix time 1136239445 czyli "01/02 03:04:05PM '06 -0700") i zapisujesz ją w takim formacie jaki chcesz zobaczyć. Go rozpoznaje po liczbach która część jest która.

Może nawet się to nie posypie po 2099 :-)

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

  • +5
# Maj 12, 2015, 15:42:36
Ja generalnie używam swoich wrapperków.

Do printowania (link):
std::string format(const char *fmt, ...);
Do parsowania (link):
void ParseWhitespace(const char *&s);
int         ParseHex(const char *&s);
int ParseInt(const char *&s);
float ParseFloat(const char *&s);
void ParseString(const char *&s,std::string &out);
std::string ParseString(const char *&s);
void ParseStringT(const char *&s,std::string &out,const char *terminators);
void        ParseHexBuffer(const char *&s,std::vector<byte> &buff);

Ale jeżeli chodzi o parsowanie rzeczy bardziej złożonych (pokroju własnego języka), to oczywiście compiler compiler (napisany samemu - nie używam żadnych żubrowatych).

Cytuj
U nich date format string wygląda np: "Mon, 02 Jan 2006" albo "2006-01-02". I działa!
Brzmi to nieco magicznie. Bałbym się że można się naciąć na jakiś format, jak przykładowo w C++ gdzie raz przez locale spodziewał mi się przy parsowaniu floatów przecinków zamiast kropek (oczywiście wypisywał też z przecinkami). I weź to potem podeślij do innego programu.

Offline Xender

  • Użytkownik

  • +3
# Maj 12, 2015, 15:52:10
@Kos - Ciekawe, chociaż średnio jestem przekonany do tego pomysłu.

Ale sam format referencyjny to jakiś pochlastany, winno być "2001-02-03 04:05:06 -0700", jeśli już.
Honor programisty nakazuje używać ISO, już szczególnie przy definiowaniu API (z poprawką na oddzielanie godziny od daty za pomocą spacji, to T jest debilne :P).

Aczkolwiek, jeśli to nie ma więcej pól, to czemu nie użyć po prostu jako znaczników YYYY-MM-DD hh:mm:ss i wariacji?

Offline Xion

  • Moderator
    • xion.log

# Maj 12, 2015, 18:22:19
@Kos: Taki myk posypie przy każdym niestandardowym/niepełnym formacie (np. mm:ss zamiast HH:mm), nie wspominając już o problemach jak:

* 4/5/2015 -- D/M/YYYY czy M/D/YYYY?
* 11:20 -- 12- czy 24-godzinny format?

Offline timus

  • Użytkownik

# Maj 12, 2015, 19:42:19
Dla C++ jest jeszcze jedna fajna biblioteka przypominająca str.format z pythona: http://fastformat.sourceforge.net/

Offline Kos

  • Użytkownik
    • kos.gd

  • +1
# Maj 12, 2015, 19:57:33
Fajny ten cppformat, ale implementuje tylko połowę Pythonowego str.format, tj. {0:ciekawostki}. W Pythonie najwięcej radości jest po lewej stronie od dwukropka, bo można się odnosić do dowolnych struktur danych:

>>> a = {'foo': 10, 'bar': [1, 2, 3]}
>>> 'Foo is {0[foo]} and one of bars is {0[bar][2]}'.format(a)
'Foo is 10 and one of bars is 3'

więc wystarczy do .format() podać jeden argument (obiekt) i w samym format stringu zdefiniować które pola / wartości chcemy wypisać (z dowolnym zagnieżdżeniem).

Ale w C++ chyba to zobaczę dopiero jak dojdzie jakaś normalna refleksja :(

Offline BrunonDEV

  • Użytkownik
    • Construgia -- RPG

# Maj 12, 2015, 19:59:47
Cytuj
nie wspominając już o problemach jak:

* 4/5/2015 -- D/M/YYYY czy M/D/YYYY?
* 11:20 -- 12- czy 24-godzinny format?

Co to za problem ustalić to wszystko z góry?

Offline Xender

  • Użytkownik

# Maj 12, 2015, 22:39:32
@Xion - Z tego, co rozumiem, to działa tak, że format stringiem jest ta konkretna data, którą podał Kos - 「(unix time 1136239445 czyli "01/02 03:04:05PM '06 -0700")」.
Więc będzie jednoznaczne, ale ja tam i tak nie widzę żadnej przewagi nad YYYY-MM-DD hh:mm:ss.
Chyba, że jednak ma jakieś większe możliwości.

Natomiast rzeczywiście, obie te opcje posypią się na pytaniu "format 12 czy 24-godziny".

Fajny ten cppformat, ale implementuje tylko połowę Pythonowego str.format, tj. {0:ciekawostki}. W Pythonie najwięcej radości jest po lewej stronie od dwukropka, bo można się odnosić do dowolnych struktur danych:
[...]
więc wystarczy do .format() podać jeden argument (obiekt) i w samym format stringu zdefiniować które pola / wartości chcemy wypisać (z dowolnym zagnieżdżeniem).

Ale w C++ chyba to zobaczę dopiero jak dojdzie jakaś normalna refleksja :(
Ileż to razy do str.format przekazywałem **locals(). :)

Dałoby się zrobić takie coś, przekazując mapę / inny kontener asocjacyjny.
Wyciągnąć z format stringa fragment pomiędzy "[]" i odwołać się po tym do przekazanego kontenerka to żaden problem.
Za to z odwoływaniem się do składowych obiektów po "{foo.bar}", umarł w butach.

Dojścia refleksji do C++ się nie spodziewam.
Runtime'owych nie, bo kłóci się z założeniem projektowym "kompilowane bezpośrednio na krzem" (a ów krzem czasem jest 8-bitowy, taktowany w MHz czy nawet kHz (zegarkowym kwarcem), a RAM ma liczony w setkach bajtów / pojedynczych KiB. Dokładniej zwie się toto AVR.). Chociaż wyjątki i RTTI już psują to założenie, więc dunno.

Dojścia lepszego metaprogramowania też się nie spodziewam, to z kolei byłoby chyba za dużo zawracania głowy.
Potężne systemy makr, takie, jak ma Lisp czy Nim*, to fajna sprawa, ale coś czuję, że to nie jest dobry moment na dodawanie czegoś takiego do C++.

*
http://nim-lang.org/docs/tut2.html#templates
http://nim-lang.org/docs/tut2.html#macros

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

  • +3
# Maj 12, 2015, 22:56:38
Cytuj
Dojścia refleksji do C++ się nie spodziewam.
Runtime'owych nie, bo kłóci się z założeniem projektowym "kompilowane bezpośrednio na krzem" (a ów krzem czasem jest 8-bitowy, taktowany w MHz czy nawet kHz (zegarkowym kwarcem), a RAM ma liczony w setkach bajtów / pojedynczych KiB. Dokładniej zwie się toto AVR.). Chociaż wyjątki i RTTI już psują to założenie, więc dunno.
Na platformy z kHz zegarem i RAM w setkach bajtów pełnego C++ i tak nie uświadczych, bo odpada cały std. Ba, odpada najczęściej nawet biblioteka w przypadku C nie tylko z uwagi na to że zmienne pokroju errno zajmowały by połowę RAMu, lecz również na to że w takim AVR na dobrą sprawę nawet nie wiadomo gdzie jest standardowe I/O.

Ale pomijając powyższe - linkowanie na poziomie pojedynczych bloków danych (np. funkcji) mamy już od dawna, więc taka refleksja mogła by nie zajmować kompletnie nic o ile nie zostanie faktycznie wykorzystana.

Cytuj
Dojścia lepszego metaprogramowania też się nie spodziewam, to z kolei byłoby chyba za dużo zawracania głowy.
Potężne systemy makr, takie, jak ma Lisp czy Nim*, to fajna sprawa, ale coś czuję, że to nie jest dobry moment na dodawanie czegoś takiego do C++.
Tyle że makra C++ już teraz są w stanie zrobić refleksję dla prostych przypadków. ;)

Przykład: ;)
#define STRUCT(name,st) \
struct name { \
st \
static const char *GetName() { return #name; } \
static const char *GetMembers() { return #st; } \
}; \

STRUCT( Foo,
    int x;
    int y;
    int z;
);

...

printf("%s\n", Foo::GetMembers());

Offline Paweł

  • Użytkownik

  • +1
# Maj 12, 2015, 23:46:38
Xender:
Co powiesz na :
printf2("{h} {w}! The answer: {answer}", "w"_a = "world", "answer"_a = 42, "h"_a = "Hello");
?
Tu implementacja używająca rozszerzenia GCC: https://bitbucket.org/p2rkw/namedarguments/overview
« Ostatnia zmiana: Maj 12, 2015, 23:49:22 wysłana przez Paweł »

Offline ArekBal

  • Użytkownik

# Maj 13, 2015, 00:14:18

Offline kubera

  • Użytkownik
    • Prywatna strona

# Maj 13, 2015, 00:42:34
Zamieszczam swoje rozwiązanie, wrzucam pliki z konwerterami.
Stosuję mało wydajne, lecz wygodne templaty:
#pragma once
#include <sstream>
namespace Monad
{
 namespace Convert
 {
  struct CParseStream : public std::wistringstream
  {
   CParseStream(const std::wstring & s);
   void ThrowIfFail();
  };
  struct CIntoStream : public std::wostringstream
  {
   CIntoStream();
  };

  template
  <typename TYPE>
   TYPE TypeFromString(const std::wstring& s)
  {
   TYPE t;
   CParseStream iss(s);
   iss >> t;
   iss.ThrowIfFail();
   return t;
  }
  template
  <typename TYPE>
  typename std::enable_if<std::is_integral<TYPE>::value, std::wstring>::type StringFromType(const TYPE pVal)
  {
   CIntoStream oss;
   oss << pVal;
   return oss.str();
  }
  template
  <typename TYPE>
  typename std::enable_if<!std::is_integral<TYPE>::value, std::wstring>::type StringFromType(const TYPE & pVal)
  {
   CIntoStream oss;
   oss << pVal;
   return oss.str();
  }
  template
  <typename TYPE>
  typename std::enable_if<std::is_integral<TYPE>::value, std::wstring>::type StringFromTypeHex(const TYPE pVal)
  {
   CIntoStream oss;
   oss << L"0x" << std::hex << pVal;
   return oss.str();
  }
  std::wostream& operator <<(std::wostream& stream, const SYSTEMTIME & pSystemTime);
 }
}
#include "pch.h"
#include <iomanip>
#include "MonadConvert.h"

namespace Monad
{
 namespace Convert
 {
  CParseStream::CParseStream(const std::wstring & s)
   : std::wistringstream(s)
  {
   imbue(std::locale::classic());
  }
  void CParseStream::ThrowIfFail()
  {
   if (bad())
    throw std::bad_cast();
  }
  CIntoStream::CIntoStream()
  {
   imbue(std::locale::classic());
  }
  std::wostream& operator <<(std::wostream& stream, const SYSTEMTIME & pSystemTime)
  {
   stream << pSystemTime.wYear
    << L'-'
    << std::setw(2) << std::setfill(L'0') << pSystemTime.wMonth
    << std::setw(0) << L'-'
    << std::setw(2) << pSystemTime.wDay
    << std::setw(0) << L'#'
    << std::setw(2) << pSystemTime.wHour
    << std::setw(0) << L':'
    << std::setw(2) << pSystemTime.wMinute
    << std::setw(0) << L':'
    << std::setw(2) << pSystemTime.wSecond;
   return stream;
  }
 }
}
W zasadzie nie muszę znać typu, który konwertuję.
W trudnych przypadkach może to rozwiązanie wykazywać wady.

Offline Xender

  • Użytkownik

# Maj 13, 2015, 11:44:34
@Krzysiek K. - Desperacja.

@Paweł - Powiem, że jak na lukier składniowy, to więcej zaciemnia, niż pomaga. Znów, wygląda po prostu desperacko.

@kubera - Nie widzę tu ani jednego format stringa.

Offline kubera

  • Użytkownik
    • Prywatna strona

# Maj 13, 2015, 12:36:34
@up, przepraszam, źle pojąłem temat.