C++11: type-safe printf



  • Ich glaube Sone ist der erst oder zweitbeste Template Meta Progger im ganzen Forum.

    Der beste ist camper. Ich kenne aber niemanden, der sich damit in diesem Forum sonst viel beschäftigt, daher bedeutet mir dein Lob nichts.

    template <int N> 
    struct foo<N, typename std::enable_if<(0<=N)&&(N<10)>::type> : std::integral_constant<int, N> {};
    

    Bei jeder weiteren partiellen Spezialisierung muss dann der letzte Parameter ebenfalls vorgegeben werden (und nicht weggelassen, da er ja ein default-Argument hat), damit diese Spezialisierungen weniger spezialisiert sind und keine Ambiguität ensteht. (Richtig?)

    Also etwa nach

    template <int N, typename fill_param> 
    struct foo<N, fill_param> : ...
    

    (Hier ist das natürlich ein schlechtes Beispiel)


  • Mod

    sone_logoff schrieb:

    template <int N> 
    struct foo<N, typename std::enable_if<(0<=N)&&(N<10)>::type> : std::integral_constant<int, N> {};
    

    Bei jeder weiteren partiellen Spezialisierung muss dann der letzte Parameter ebenfalls vorgegeben werden (und nicht weggelassen, da er ja ein default-Argument hat), damit diese Spezialisierungen weniger spezialisiert sind und keine Ambiguität ensteht. (Richtig?)

    Es muss so gestaltet werden, dass keine Überlappung entsteht, d.h. die enable_if-Bedingungen müssen so formuliert werden, dass die Bedingungen zweier partieller Spezialisierung für keine mögliches Argument gleichzeitig wahr werden. Das Defaultargumtent steht dort nur da, um die Benutzung des Templates zu vereinfachen.

    Ist die Menge der zu betrachtenden Fälle bekannt, würde ich in der Regel eher zu conditional (oder einem allgemeineren select-Template für N Fälle) greifen. Die andere Variante hat vor allem den Vorteil, dass man sie beliebig um weitere Fälle erweitern kann, ohne das Primärtemplate anfassen zu müssen.



  • sone_logoff schrieb:

    Ich glaube Sone ist der erst oder zweitbeste Template Meta Progger im ganzen Forum.

    Der beste ist camper. Ich kenne aber niemanden, der sich damit in diesem Forum sonst viel beschäftigt, daher bedeutet mir dein Lob nichts.

    Der Post war nur dazu da, um Gegenreaktionen zu provozieren.



  • Danke, camper, den Trick hatte ich völlig vergessen. Hier auch eingebaut:

    #include <iostream>
    
    template<typename T>
    struct identity
    {
    	using type = T;
    };
    
    template<char ... Ch>
    struct const_string : identity<const_string<Ch...>>
    {
    	static char constexpr nt_arr[]{ Ch..., '\0' };
    
    	static std::size_t constexpr length = sizeof...(Ch);
    
    private:
    	constexpr std::size_t _count_impl( char c, std::size_t index )
    	{
    		return nt_arr[index] == c + (index == 0 ? 0 : _count_impl(c, index - 1));
    	}
    
    public:
    	constexpr std::size_t count( char c )
    	{ return _count_impl( c, sizeof(nt_arr) - 1 );  }
    };
    
    template<char ... Ch>
    char constexpr const_string<Ch...>::nt_arr[];
    
    /// type_list ///////////////////////////////////////////////////////////////////////
    
    // Geht natürlich auch ohne Rekursion, ist nunmal auf die Schnelle gemacht:
    template<std::size_t index, typename T, typename ... Types>
    struct at : at<index-1, Types...> {};
    template<typename T, typename ... Types>
    struct at<0, T, Types...> : identity<T> {};
    
    template<typename ... Types>
    struct type_list : identity<type_list<Types...>>
    {
    	static std::size_t constexpr length = sizeof...(Types);
    
    	template<std::size_t index>
    	struct get : at<index, Types...> {};
    };
    
    template<typename... Args>
    type_list<Args...> arg_type_list( Args&&... );
    
    template<std::size_t i, typename T>
    struct indexed_type
    {
    	static constexpr std::size_t index = i;
    	using type = T;
    };
    
    /// concat_type_lists //////////////////////////////////////////////////////////
    
    template<typename, typename> struct concat_type_lists;
    
    template<typename ... first,
             typename ... second>
    struct concat_type_lists<type_list<first...>, type_list<second...>> : type_list<first..., second...> {};
    
    /// ////////////////////////////////////////////////////////////7777
    
    constexpr bool is_digit( char c )
    {
    	return c >= '0' and c <= '9';
    }
    
    template<int Val>
    struct int_ : std::integral_constant<int, Val> {};
    
    template<typename First, typename ... T> struct get_last : get_last<T...> {};
    template<typename First> struct get_last<First> : identity<First> {};
    
    template<typename, typename> struct replace_last;
    template<typename New, typename First, typename... Types>
    struct replace_last<New, type_list<First, Types...>> :
    	concat_type_lists<type_list<First>, typename replace_last<New, type_list<Types...>>::type> {};
    template<typename New, typename First>
    struct replace_last<New, type_list<First>> : type_list<New> {};
    
    /// ////////////////////////////////////////////////////////////////////////////////////////////////
    
    #include <iomanip>
    
    template<char C, int arg = 0> struct conversion_flag;
    
    #define SPECIALIZE_CONVERSION_FLAG( ch, ... ) \
    template<> \
    struct conversion_flag<ch> : std::integral_constant<char, ch> \
    { \
    	template<typename CharT, \
    	         typename Traits> \
    	static std::basic_ostream<CharT, Traits>& print( std::basic_ostream<CharT, Traits>& os ) \
    	{ return os << (__VA_ARGS__); } \
    }
    
    #define SPECIALIZE_CONVERSION_FLAG_ARG( ch, ... ) \
    template<int arg> \
    struct conversion_flag<ch, arg> : std::integral_constant<char, ch> \
    { \
    	template<typename CharT, \
    	         typename Traits> \
    	static std::basic_ostream<CharT, Traits>& print( std::basic_ostream<CharT, Traits>& os ) \
    	{ return os << (__VA_ARGS__); } \
    }
    
    SPECIALIZE_CONVERSION_FLAG( 'l', std::left );
    SPECIALIZE_CONVERSION_FLAG( 'r', std::right );
    // SPECIALIZE_CONVERSION_FLAG( 'A', std::hexfloat );
    SPECIALIZE_CONVERSION_FLAG( 'f', std::fixed );
    SPECIALIZE_CONVERSION_FLAG( 'e', std::scientific );
    SPECIALIZE_CONVERSION_FLAG( 'X', std::hex );
    SPECIALIZE_CONVERSION_FLAG( 'O', std::oct );
    SPECIALIZE_CONVERSION_FLAG_ARG( 'p', std::setprecision(arg) );
    SPECIALIZE_CONVERSION_FLAG_ARG( 'w', std::setw(arg) );
    
    /// ///////////////////////////////////////////////////////////////////////////////////////////////////
    
    template<typename, typename, typename = void> struct flag_parser;
    
    #define requires( ... ) typename std::enable_if< (__VA_ARGS__) >::type
    
    template<typename ... Types, char first, char ... tail>
    struct flag_parser<type_list<Types...>, const_string<first, tail...>, requires(!is_digit(first))> :
    	flag_parser<type_list<Types..., conversion_flag<first>>, const_string<tail...>> {};
    
    template<typename ... Types, char first, char ... tail>
    struct flag_parser<type_list<Types...>, const_string<first, tail...>, requires(is_digit(first) && !std::is_same<typename get_last<Types...>::type::value_type, int>::value)> :
    	flag_parser<type_list<Types..., int_<first - '0'>>, const_string<tail...>> {};
    
    template<typename ... Types, char first, char ... tail>
    struct flag_parser<type_list<Types...>, const_string<first, tail...>, requires(is_digit(first) &&  std::is_same<typename get_last<Types...>::type::value_type, int>::value)> :
    	flag_parser< typename replace_last<int_<first - '0' + get_last<Types...>::type::value * 10>, type_list<Types...>>::type,
    	             const_string<tail...>> {};
    
    template<typename ... Types>
    struct flag_parser<type_list<Types...>, const_string<>> :
    	type_list<Types...> {};
    
    template<typename> struct flag_dispatcher;
    
    template<char ch, typename ... Args>
    struct flag_dispatcher<type_list<conversion_flag<ch>, Args...>>
    {
    	template<typename CharT,
    	         typename Traits>
    	static std::basic_ostream<CharT, Traits>& print( std::basic_ostream<CharT, Traits>& os )
    	{
    		if( conversion_flag<ch>::print(os) )
    			flag_dispatcher<type_list<Args...>>::print(os);
    
    		return os;
    	}
    };
    
    template<char ch, int arg, typename ... Args>
    struct flag_dispatcher<type_list<conversion_flag<ch>, int_<arg>, Args...>>
    {
    	template<typename CharT,
    	         typename Traits>
    	static std::basic_ostream<CharT, Traits>& print( std::basic_ostream<CharT, Traits>& os )
    	{
    		if( conversion_flag<ch, arg>::print(os) )
    			flag_dispatcher<type_list<Args...>>::print(os);
    
    		return os;
    	}
    };
    
    template<>
    struct flag_dispatcher<type_list<>>
    {
    	template<typename CharT,
    	         typename Traits>
    	static std::basic_ostream<CharT, Traits>& print( std::basic_ostream<CharT, Traits>& os )
    	{ return os; }
    };
    
    int main()
    {
    	using parser = flag_parser<type_list<>, const_string<'f', 'p', '4'>>;
    
    	flag_dispatcher<parser::type>::print(std::cout);
    	std::cout << 48.65;
    }
    

    flag_dispatcher kann man so dann in ts_printf einbauen, nachdem jemand die falsche Semantik behoben hat (keine Argumente führen zu Argument 0, zudem können unnötige Argumente an Flags übergeben werden die schlichtweg ignoriert werden).


  • Mod

    requires geht auch ohne Makro.

    template <bool b> using requires = typename std::enable_if<b>::type;
    


  • Stellt euch mal vor C++ hätte anstatt den Templates einen ordentlichen Preprozessor, wie schön das alles sein könnte. 🤡



  • Mir kann keiner weismachen, dass das noch jemand lesen kann 😮



  • template using requires = typename std::enable_if::type;
    

    ➡

    template struct requires { static_assert(b, ""); };
    

    Mir ist durchaus bewusst, dass requires auch als Alias-Template o.ä. geschrieben werden kann - mir ist aber die Syntax mit den runden Klammern wichtig. (Eine constexpr -Funktion kommt auch nicht in Betracht).


  • Mod

    template struct requires { static_assert(b, ""); };
    

    Das ist etwas völlig anderes.



  • Aber das Resultat ist das gleiche: Code kompiliert nicht, wenn Bedingung nciht erfüllt.



  • Nathan schrieb:

    Aber das Resultat ist das gleiche: Code kompiliert nicht, wenn Bedingung nciht erfüllt.

    Nö.
    Mit enable_if aka SFINAE kann man Overloads deaktivieren ohne einen Fehler zu bekommen.
    Man kann den "geenableten" Code also quasi selektiv einblenden. Also quasi "wenn nicht stimmt dann to so als ob's nicht da wäre".

    Ganz was anderes als zu sagen "wenn nicht stimmt dann Fehler".



  • hustbaer schrieb:

    Nathan schrieb:

    Aber das Resultat ist das gleiche: Code kompiliert nicht, wenn Bedingung nciht erfüllt.

    Nö.
    Mit enable_if aka SFINAE kann man Overloads deaktivieren ohne einen Fehler zu bekommen.
    Man kann den "geenableten" Code also quasi selektiv einblenden. Also quasi "wenn nicht stimmt dann to so als ob's nicht da wäre".

    Ganz was anderes als zu sagen "wenn nicht stimmt dann Fehler".

    Ja, das ist mir klar.
    Ich ging davon aus, dass Sone sein requires Makro wie sonst häufig auch genutzt hat*, indem es dort einfach nur alleine steht und dafür sorgt, dass der Code nicht kompiliert.

    *Korrigier mich, wenn ich falsch liege, habe das aber so in Erinnerung.



  • Ich bin wie immer ein wenig zu schnell, natürlich ist das so kein SFINAE. 🙂

    Mir kann keiner weismachen, dass das noch jemand lesen kann 😮

    Deswegen versuche ich eine kleine Makro-Lib zu schreiben, die solche Parser einfacher zu schreiben macht.



  • Makro-Lib

    Ich dachte, dass die Allgemeinheit mittlerweile erkannt hat, dass Makros eher schlecht sind.



  • vertippt... sorry



  • Mit dem aktuellen lässt sich folgendes schreiben - eine TMP-Funktion, die einen String nach einem Zeichen aufteilt:

    DECLARE_TOKENIZER()
    
    SPEC_CH() :
    	COND< ch == split_ch,
    		  name<INSERT_TOKEN(const_string<current_string...>), split_ch, const_string<>, STRING_INAVAIL()>,
    		  name<TOKEN_LIST(), split_ch, const_string<current_string..., ch>, STRING_INAVAIL()> > {};
    
    SPEC_CH_END() :
    	COND< ch == split_ch,
    		  INSERT_TOKEN(const_string<current_string...>, const_string<>),
    		  INSERT_TOKEN(const_string<current_string..., ch>) > {};
    

    Lösche ich später alles... sieht schlimmer aus als ohne...



  • knivil schrieb:

    Makro-Lib

    Ich dachte, dass die Allgemeinheit mittlerweile erkannt hat, dass Makros eher schlecht sind.

    Makros haben nen Haufen Probleme. Oder sagen wir Dinge die nicht gut gelöst wurden. Wo man aber drum rum arbeiten kann.
    Von diesen Unschönheiten sind sie aber extrem cool.
    Makros sind eines der Features die mit in C# am meisten abgehen.



  • hustbaer schrieb:

    Makros sind eines der Features die mit in C# am meisten abgehen.

    Ich bin verwirrt: http://stackoverflow.com/questions/709463/c-sharp-macro-definitions-in-preprocessor ... nun ist auch von 2009. Wo gibt es mehr Infos?

    Ich kenne Makrosysteme in C und in Scheme. Obwohl fuer beide der gleiche Name verwendet wird, sind sie dennoch sehr verschieden. Waehrend a) simple Textersetzung ist, ist b) ein vollstaendiges System zur Codegenerierung mit vielen Features. Wo sind Makros von C# einzuordnen?

    PS: Mit 'Makros sind schlecht' bezog ich mich auf Makros in C.



  • https://github.com/bingojamigo/VTMPL

    Da mir das ganze hin und her Kopiere zu viel wurde, habe ich alles in eine winzige Lib gesteckt, fortan werde ich nur die Header einbinden.

    Ich versuche in nächster Zeit mal ts_printf fertig zu machen (mit allen Formatspecifiern).

    Solange wäre Rückmeldung über die Lib nicht schlecht.


Anmelden zum Antworten