C++11: type-safe printf



  • Mit C++11' variadic templates und perfect forwarding lässt sich ein typsicheres printf schreiben.
    Mein Ansatz mit Assertions für Fehlermeldung (falsche Formatstrings sehe ich als Programmierfehler, lässt sich ggf. durch Policies noch anpassbar machen):

    #include <iostream>
    #include <set>
    #include <stdexcept>
    #include <string>
    #include <cassert>
    
    template<typename Char,
             typename Traits>
    void ts_printf_impl( std::basic_ostream<Char, Traits>& os,
    				  Char const* format,
    				  Char const term,
    				  Char const percent_sign )
    {
    	while( !Traits::eq(*format, term) )
    	{
    		bool format_sign = !Traits::eq(*format, percent_sign)
    		                || (Traits::eq(*format, percent_sign) && Traits::eq(*++format, percent_sign));
    
    		assert( format_sign && "Invalid format string - too much %'s!" );
    
    		if( Traits::eq_int_type(os.rdbuf()->sputc(*format), Traits::eof()) )
    		{
    			os.setstate( std::ios_base::badbit );
    			break;
    		}
    
    		++format;
    	}
    }
    
    template<typename Char,
             typename Traits,
             typename FirstArg,
             typename ...Args>
    void ts_printf_impl( std::basic_ostream<Char, Traits>& os,
    				     Char const* format,
    				     Char const term,
    				     Char const percent_sign,
    				     FirstArg&& first,
    				     Args&&... tail)
    {
    	auto& buf = *os.rdbuf();
    
    	bool putted_val = false;
    
    	while( not Traits::eq(*format, term) )
    	{
    		if( Traits::eq(*format, percent_sign)
    		 && not Traits::eq(*++format, percent_sign) )
    		{
    			--format;
    
    			if( !(os << std::forward<FirstArg>(first)) )
    				os.setstate( std::ios_base::failbit ); // Falls badbit gesetzt wurde.
    
    			putted_val = true;
    
    			break;
    		}
    
    		if( Traits::eq_int_type(buf.sputc(*format), Traits::eof()) )
    		{
    			os.setstate( std::ios_base::badbit );
    			putted_val = true;
    			break;
    		}
    
    		++format;
    	}
    
    	assert( putted_val && "You provided to much function arguments!" );
    
    	if( os )
    		ts_printf_impl( os, format + 1, term, percent_sign, std::forward<Args>(tail)... );
    }
    
    template<typename Char,
             typename Traits,
             typename... Arg>
    std::basic_ostream<Char, Traits>& ts_printf( std::basic_ostream<Char, Traits>& os,
    											 Char const* format,
    											 Arg&&... tail )
    {
    	typename std::basic_ostream<Char, Traits>::sentry ok{os};
    
    	if( ok ) try
    	{
    		Char const term = os.widen('\0'),
    		           percent_sign = os.widen('%');
    
    		ts_printf_impl(os, format, term, percent_sign, std::forward<Arg>(tail)...);
    	}
    	catch( std::ios::failure const& )
    	{ throw; }
    	catch(...)
    	{
    		os.clear( os.rdstate() | std::ios_base::badbit );
    		if( os.exceptions() & std::ios_base::badbit )
    			throw;
    	}
    
    	return os;
    }
    
    int main()
    {
    	ts_printf( std::cout, "Ein %eispiel mit Printf: % geteilt durch % ergibt: %", 'B', 7, 8, 7./8 );
    }
    

    Ich hatte ursprünglich einen nicht-rekursiven Ansatz mit

    #define DO_VARIADIC(...) ((void)std::initializer_list<char>{((__VA_ARGS__),'\0')...})
    

    den ich allerdings verwarf.

    Ob das stumme Weiterleiten von ios::failure -Exceptions richtig ist, ist mir auch nicht klar.

    Ist auch nicht perfektioniert, daher frage ich vor Allem was falsch ist, und was sich noch verbessern lässt.

    MfG

    Edit: Ja, ich sehe: Da sind not, or mit ! und den anderen vermischt. Davon mal abgesehen (fixe ich gleich).



  • Mit C++11' variadic templates und perfect forwarding lässt sich ein typsicheres printf schreiben.

    Gab es da nicht schon 1000 und eins11elf Videotutorials dazu? Vielleicht die mal ansehen ...

    Ich empfinde type-safe printf nur als Spielerei und hatte auch nie Probleme mit dem eigentlichen printf. Fuer das Problem voellig overengineered.



  • Funktionsargumente und %-Zeichen kann man zählen, btw. 😉
    Interessant wäre eigentlich nur ein printf das einen constexpr-String nimmt und bei Formatierungsfehlern erst gar nicht kompiliert. Insbesondere fehlt aber das, was printf eigentlich so toll macht: das f.



  • cooky451 schrieb:

    Funktionsargumente und %-Zeichen kann man zählen, btw. 😉
    Interessant wäre eigentlich nur ein printf das einen constexpr-String nimmt und bei Formatierungsfehlern erst gar nicht kompiliert.

    Ja, das ist toll. Das mache ich. 👍 Sezt' mich gleich dran.

    Insbesondere fehlt aber das, was printf eigentlich so toll macht: das f.

    Hmm.

    Gab es da nicht schon 1000 und eins11elf Videotutorials dazu? Vielleicht die mal ansehen ...

    Wo das? oO



  • GoingNative 2011 oder/und 2012. Kennste? Hier Folien: http://ecn.channel9.msdn.com/events/GoingNative12/GN12VariadicTemplatesAreFunadic.pdf

    keine Ahnung, ob es genau das trifft. Gibt mehr, suche alleine weiter.



  • Entschuldigt, mir war die letzten zwei Tage nicht möglich zu Programmieren.

    #include <iostream>
    #include <set>
    #include <stdexcept>
    #include <string>
    #include <functional>
    
    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[];
    
    /// concat_strings //////////////////////////////////////////////////////////////////
    
    template<typename, typename> struct concat_strings;
    
    template<char ... first,
             char ... second>
    struct concat_strings<const_string<first...>, const_string<second...>> : const_string<first..., second...> {};
    
    /// 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...> {};
    };
    
    /// 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...> {};
    
    /// split ////////////////////////////////////////////////////////////////////////
    
    template<char, typename, typename, typename> struct split_on;
    
    template<char token,
             typename ... strings>
    struct split_on< token, type_list<strings...>, const_string<>, const_string<> > : type_list<strings...> {};
    
    template<char token,
             char ... last,
             typename ... strings>
    struct split_on< token, type_list<strings...>, const_string<last...>, const_string<> > : type_list<strings..., const_string<last...>> {};
    
    template<char token,
             char ... last,
             typename ... strings>
    struct split_on< token, type_list<strings...>, const_string<last...>, const_string<token> > : type_list<strings..., const_string<last...>, const_string<>> {};
    
    template<char token,
             char current,
             char ... last,
             char ... tail,
             typename ... strings>
    struct split_on< token, type_list<strings...>, const_string<last...>, const_string<current, tail...> > : split_on<token, type_list<strings...>, const_string<last..., current>, const_string<tail...>> {};
    
    template<char token,
             char ... last,
             char ... tail,
             typename ... strings>
    struct split_on< token, type_list<strings...>, const_string<last...>, const_string<token, tail...> > : split_on<token, type_list<strings..., const_string<last...>>, const_string<>, const_string<tail...>> {};
    
    template<char token,
             char ... last,
             char ... tail,
             typename ... strings>
    struct split_on< token, type_list<strings...>, const_string<last...>, const_string<token, token, tail...> > : split_on<token, type_list<strings...>, const_string<last..., token, token>, const_string<tail...>> {};
    
    template<char c, typename> struct split;
    template<char c, char... chars> struct  split<c, const_string<chars...>> : split_on<c, type_list<>, const_string<>, const_string<chars...>> {};
    
    /// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    template<char, typename> struct rtrim;
    template<char token, char first, char ... ch>
    struct rtrim<token, const_string<first, ch...>> : concat_strings<const_string<first>, typename rtrim<token, const_string<ch...>>::type> {};
    template<char token, char ... ch>
    struct rtrim<token, const_string<token, ch...>> : const_string<> {};
    template<char token>
    struct rtrim<token, const_string<>> : const_string<> {};
    
    /// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    template<char ... Ch>
    struct ts_printf_env
    {
    	using format_string = typename rtrim<'\0', const_string<Ch...>>::type;
    
    	static char constexpr format_sign = '%';
    
    	using string_list = typename split<format_sign, format_string>::type;
    
    	template<typename CharT,
    	         typename Traits>
    	struct io_env
    	{
        private:
    
    		template<std::size_t type_list_index>
    		static void ts_printf_impl( std::basic_ostream<CharT, Traits>& os )
    		{
    			using to_put = typename string_list::template get<type_list_index>::type;
    
    			if( os.rdbuf()->sputn( to_put::nt_arr, to_put::length ) != to_put::length )
    				os.setstate( std::ios_base::badbit );
    		}
    
    		template<std::size_t type_list_index,
    		         typename FirstArg,
    				 typename ...Args>
    		static void ts_printf_impl( std::basic_ostream<CharT, Traits>& os,
    							        FirstArg&& first,
    							        Args&&... tail)
    		{
    			ts_printf_impl<type_list_index>( os );
    
    			if( os << std::forward<FirstArg>(first) )
    				ts_printf_impl<type_list_index + 1>( os, std::forward<Args>(tail)... );
    		}
    
    	public:
    
    		template<typename ... Args>
    		static std::basic_ostream<CharT, Traits>& ts_printf( std::basic_ostream<CharT, Traits>& os, Args&&... args )
    		{
    			static_assert( sizeof...(Args) + 1 == string_list::length, "Invalid format string!" );
    
    			typename std::basic_ostream<CharT, Traits>::sentry ok{os};
    
    			if( ok ) try
    			{
    				ts_printf_impl<0>( os, std::forward<Args>(args)... );
    			}
    			catch( std::ios::failure const& )
    			{ throw; }
    			catch(...)
    			{
    				os.clear( os.rdstate() | std::ios_base::badbit );
    				if( os.exceptions() & std::ios_base::badbit )
    					throw;
    			}
    
    			return os;
    		}
    	};
    };
    
    #define SPLIT_1(str,pos)  str [ pos < sizeof str ? pos : sizeof(str) - 1 ]
    #define SPLIT_2(str,x)  SPLIT_1(str,x), SPLIT_1(str,x + 1)
    #define SPLIT_4(str,x)  SPLIT_2(str,x), SPLIT_2(str,x + 2)
    #define SPLIT_8(str,x)  SPLIT_4(str,x), SPLIT_4(str,x + 4)
    #define SPLIT_16(str,x) SPLIT_8(str,x), SPLIT_8(str,x + 8)
    #define SPLIT_32(str,x) SPLIT_16(str,x), SPLIT_16(str,x + 16)
    #define SPLIT_64(str,x) SPLIT_32(str,x), SPLIT_32(str,x + 32)
    #define SPLIT_128(str,x) SPLIT_64(str,x), SPLIT_64(str,x + 64)
    
    #define ts_printf(os, format, ...) ts_printf_env<SPLIT_128(format, 0)>::io_env<decltype(os)::char_type, decltype(os)::traits_type>::ts_printf( os, __VA_ARGS__ )
    
    int main()
    {
    	ts_printf( std::cout, "Ein %eispiel mit Printf: % geteilt durch % ergibt: %", 'B', 7, 8, 7./8 );
    }
    

    Ein bekannter Bug ist, dass das Komma im Makro bei keinen Argumenten "unnötig" da ist, und dadurch die Syntax falsch ist. BOOST_PP_COMMA_IF wäre vielleicht eine Option, aber das muss doch einfacher gehen...



  • Schade, das dein printf nicht die selben Formatspezifizierer enthält, wie das original printf. Genau die waren ja das Hübsche da dran.



  • Genau die waren ja das Hübsche da dran.

    Das wird vielleicht eine Herausforderung, ich versuche mal was zu machen, ...



  • Schön fände ich auch das:

    ts_printf("%{1} + %{2} = %{0}", 10, 6, 4);
    


  • otze schrieb:

    Schade, das dein printf nicht die selben Formatspezifizierer enthält, wie das original printf. Genau die waren ja das Hübsche da dran.

    Genau das hat mich immer daran gestoert. Redundante, nutzlose Typangaben.

    Ethon schrieb:

    Schön fände ich auch das:

    ts_printf("%{1} + %{2} = %{0}", 10, 6, 4);
    

    Das hier ist tricky. Bin mal gespannt, ob Sone das hinbekommt. 😉



  • Kellerautomat schrieb:

    otze schrieb:

    Schade, das dein printf nicht die selben Formatspezifizierer enthält, wie das original printf. Genau die waren ja das Hübsche da dran.

    Genau das hat mich immer daran gestoert. Redundante, nutzlose Typangaben

    Genau. Und damit auch eine häufige und unnötige Fehlerquelle, z.B. wenn sich der Typ ändert oder man sich mit dem Flag vertut. Davon abgesehen muss man die Anzahl der Typen im Voraus beschränken.

    Ethon schrieb:

    Schön fände ich auch das:

    ts_printf("%{1} + %{2} = %{0}", 10, 6, 4);
    

    Ja, so einen Formatstring finde ich auch besser. Gibts einen Grund für die geschweiften Klammern? Sonst fände ich %1 kompakter, vereinfacht auch das Parsen.

    <💡>



  • Stimmt, etwas verpeilt. Wohl nur hübsch lösbar wenn der Formatstring constexpr ist.



  • Kellerautomat schrieb:

    otze schrieb:

    Schade, das dein printf nicht die selben Formatspezifizierer enthält, wie das original printf. Genau die waren ja das Hübsche da dran.

    Genau das hat mich immer daran gestoert. Redundante, nutzlose Typangaben.

    Und dafür kannst du bei floats angeben, wiviele nachkommastellen sie haben sollen wie sie formatiert sind...all das geht hier ohne diese redundanten nutzlosen Typangaben vollkommen verloren.



  • Ich würde vorschlagen sich an Boost.Format zu orientieren.



  • otze schrieb:

    Und dafür kannst du bei floats angeben, wiviele nachkommastellen sie haben sollen wie sie formatiert sind...all das geht hier ohne diese redundanten nutzlosen Typangaben vollkommen verloren.

    Vielleicht wäre ein Kompromiss gut? Ein paar spezifische Flags wie %f wie gehabt, und ein generisches wie %t , das auch mit benutzerdefinierten Typen zurecht kommt?

    <💡>



  • Ich würde vorschlagen sich am .NET Framework zu orientieren. (String.Format)



  • #include <iostream>
    #include <set>
    #include <stdexcept>
    #include <string>
    #include <functional>
    
    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[];
    
    /// concat_strings //////////////////////////////////////////////////////////////////
    
    template<typename, typename> struct concat_strings;
    
    template<char ... first,
             char ... second>
    struct concat_strings<const_string<first...>, const_string<second...>> : const_string<first..., second...> {};
    
    /// 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...> {};
    
    /// split ////////////////////////////////////////////////////////////////////////
    
    template<char, typename, typename, typename> struct split_on;
    
    template<char token,
             typename ... pairs>
    struct split_on< token, type_list<pairs...>, const_string<>, const_string<> > : type_list<pairs...> {};
    
    template<char token,
             char ... last,
             typename ... pairs>
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<> > : type_list< pairs..., indexed_type<0, const_string<last...>> > {};
    
    // Kein Token vorhanden:
    template<char token,
             char current,
             char ... last,
             char ... tail,
             typename ... pairs>
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<current, tail...> > : split_on<token, type_list<pairs...>, const_string<last..., current>, const_string<tail...>> {};
    
    // Token ist das letzte Zeichen:
    #define D1( X ) \
    \
    template<char token, \
             char ... last, \
             typename ... pairs> \
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<token, #X[0]> > : \
    	type_list<pairs..., indexed_type<X, const_string<last...>>, indexed_type<0, const_string<>>> {}; \
    \
    template<char token, \
             char ... last, \
             char ... tail, \
             typename ... pairs> \
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<token, #X[0], tail...> > : \
    	split_on<token, type_list< pairs..., indexed_type<X, const_string<last...>> >, const_string<>, const_string<tail...>> {};
    
    D1(0) D1(1) D1(2) D1(3) D1(4) D1(5) D1(6) D1(7) D1(8) D1(9)
    
    template<char token,
             char ... last,
             char ... tail,
             typename ... pairs>
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<token, token, tail...> > : split_on<token, type_list<pairs...>, const_string<last..., token, token>, const_string<tail...>> {};
    
    template<char c, typename> struct split;
    template<char c, char... chars> struct  split<c, const_string<chars...>> : split_on<c, type_list<>, const_string<>, const_string<chars...>> {};
    
    /// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    template<char, typename> struct rtrim;
    template<char token, char first, char ... ch>
    struct rtrim<token, const_string<first, ch...>> : concat_strings<const_string<first>, typename rtrim<token, const_string<ch...>>::type> {};
    template<char token, char ... ch>
    struct rtrim<token, const_string<token, ch...>> : const_string<> {};
    template<char token>
    struct rtrim<token, const_string<>> : const_string<> {};
    
    /// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    template<std::size_t Index, typename T>
    struct rack
    {
    	static constexpr std::size_t index = Index;
    
    	T&& arg;
    
    	rack( T&& a ):
    		arg( std::forward<T>(a) ) {}
    };
    
    /// index-list (v. camper)
    
    template<int ... args>
    struct index_list
    {
        static constexpr int arr[]{args...};
        using type = index_list;
    };
    
    template <typename T, typename U> struct concat_list;
    template <int... i, int... j>
    struct concat_list<index_list<i...>,index_list<j...>>
    : index_list<i..., (sizeof...(i)+i)..., (2*sizeof...(i)+j)...> {};
    
    template <int N> struct make_index_list
    : concat_list<typename make_index_list<N/2>::type, typename make_index_list<N%2>::type> {};
    template <> struct make_index_list<1> : index_list<0> {};
    template <> struct make_index_list<0> : index_list<> {};
    
    /// </camper> /
    
    template<typename, typename...> struct Indicer;
    
    template<int... indices, typename... Args>
    struct Indicer<index_list<indices...>, Args...> : rack<indices, Args>...
    {
    	Indicer( Args&&... args ):
    		rack<indices, Args>(std::forward<Args>(args))... {}
    };
    
    template<typename format_string,
             typename CharT,
    	     typename Traits,
    	     typename index_list>
    struct ts_printf_env;
    
    template<typename format_string,
             typename CharT,
    	     typename Traits,
    	     int... indices>
    struct ts_printf_env<format_string, CharT, Traits, index_list<indices...>>
    {
    	static char constexpr format_sign = '%';
    
    	using string_list = typename split<format_sign, format_string>::type;
    
    private:
    
    	template<std::size_t type_list_index>
    	static void ts_printf_impl( std::basic_ostream<CharT, Traits>& os )
    	{
    		using to_put = typename string_list::template get<type_list_index>::type::type;
    
    		if( os.rdbuf()->sputn( to_put::nt_arr, to_put::length ) != to_put::length )
    			os.setstate( std::ios_base::badbit );
    	}
    
    	template<std::size_t type_list_index,
    			 typename FirstArg,
    			 typename ...Args>
    	static void ts_printf_impl( std::basic_ostream<CharT, Traits>& os,
    								FirstArg&& first,
    								Args&&... tail)
    	{
    		ts_printf_impl<type_list_index>( os );
    
    		if( os << std::forward<FirstArg>(first) )
    			ts_printf_impl<type_list_index + 1>( os, std::forward<Args>(tail)... );
    	}
    
    public:
    
    	template<typename ... Args>
    	static std::basic_ostream<CharT, Traits>& ts_printf( std::basic_ostream<CharT, Traits>& os, Args&&... args )
    	{
    		static_assert( sizeof...(Args) + 1 == string_list::length, "Invalid format string!" );
    
    		typename std::basic_ostream<CharT, Traits>::sentry ok{os};
    
    		if( ok ) try
    		{
    			Indicer<typename make_index_list<sizeof...(Args)>::type, Args...> indicer( std::forward<Args>(args)... );
    
    			#define index string_list::template get<indices>::type::index
    			ts_printf_impl<0>( os, indicer.rack< index,
    												 typename at<index, Args...>::type >::arg... );
    			#undef index
    		}
    		catch( std::ios::failure const& )
    		{ throw; }
    		catch(...)
    		{
    			os.clear( os.rdstate() | std::ios_base::badbit );
    			if( os.exceptions() & std::ios_base::badbit )
    				throw;
    		}
    
    		return os;
    	}
    };
    
    #define SPLIT_1(str,pos)  str [ pos < sizeof str ? pos : sizeof(str) - 1 ]
    #define SPLIT_2(str,x)  SPLIT_1(str,x), SPLIT_1(str,x + 1)
    #define SPLIT_4(str,x)  SPLIT_2(str,x), SPLIT_2(str,x + 2)
    #define SPLIT_8(str,x)  SPLIT_4(str,x), SPLIT_4(str,x + 4)
    #define SPLIT_16(str,x) SPLIT_8(str,x), SPLIT_8(str,x + 8)
    #define SPLIT_32(str,x) SPLIT_16(str,x), SPLIT_16(str,x + 16)
    #define SPLIT_64(str,x) SPLIT_32(str,x), SPLIT_32(str,x + 32)
    #define SPLIT_128(str,x) SPLIT_64(str,x), SPLIT_64(str,x + 64)
    
    #define ts_printf(os, format, ...) ts_printf_env<typename rtrim<'\0', const_string<SPLIT_128(format, 0)>>::type, \
                                                     decltype(os)::char_type, \
                                                     decltype(os)::traits_type, \
                                                     make_index_list<decltype(arg_type_list(__VA_ARGS__))::length>::type>::ts_printf( os, __VA_ARGS__ )
    
    int main()
    {
    	ts_printf(std::cout, "%1 + %2 = %0", 10, 6, 4);
    }
    

    Achtung: Kaum getestet(!).

    Kleine Notiz: Laut aktuellem Prinzip (erweiterbar, erhöht allerdings Kompilierzeit quadratisch o.ä.) sind nur 10 Argumente möglich, da alle Spezialisierungen fürs Parsen der Zahl (per Makro) explizit deklariert werden müssen. Unschön. camper weiß sicher, wie es besser geht. Vielleicht ginge auch irgendwas mit SFINAE...



  • Entschuldigt, ich bin heute nicht ganz frisch im Kopf. Schlecht geschlafen, ihr versteht.

    template<char C>
    struct enable_if_digit
    {
    	static_assert( C >= '0' && C <= '9', "Not a digit!" );
    
    	static constexpr char ch = C;
    	static constexpr int number = C - '0';
    };
    
    template<char token,
             char digit,
             char ... last,
             typename ... pairs>
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<token, digit> > :
    	type_list<pairs..., indexed_type<enable_if_digit<digit>::number, const_string<last...>>, indexed_type<0, const_string<>>> {};
    
    template<char token,
             char digit,
             char ... last,
             char ... tail,
             typename ... pairs>
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<token, digit, tail...> > :
    	split_on<token, type_list< pairs..., indexed_type<enable_if_digit<digit>::number, const_string<last...>> >, const_string<>, const_string<tail...>> {};
    

    http://ideone.com/4dCcgw

    Das lässt sich so auch sehr leicht auf höhere Zahlen erweitern:

    template<char token,
             char digit, char digit2,
             char ... last,
             typename ... pairs>
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<token, digit, digit2> > :
    	type_list<pairs..., indexed_type<enable_if_digit<digit>::number * 10 + enable_if_digit<digit2>::number, const_string<last...>>, indexed_type<0, const_string<>>> {};
    
    template<char token,
             char digit, char digit2,
             char ... last,
             char ... tail,
             typename ... pairs>
    struct split_on< token, type_list<pairs...>, const_string<last...>, const_string<token, digit, digit2, tail...> > :
    	split_on<token, type_list< pairs..., indexed_type<enable_if_digit<digit>::number * 10 + enable_if_digit<digit2>::number, const_string<last...>> >, const_string<>, const_string<tail...>> {};
    

    (Ungetestet, Edit: Geht so nicht)
    Ich habe dennoch das Gefühl, ich mache mir unnötigerweise das Leben mit split_on schwer....


  • Mod

    Sone schrieb:

    Vielleicht ginge auch irgendwas mit SFINAE...

    Das ist technisch gesehen zwar kein SFINAE, aber die selben Prinzipien können nat. angewandt werden (und bei einem nur intern verwendeten Template stört ein zusätzlicher Parameter nicht).
    Zum Beispiel:

    #include <utility>
    
    template <int N, typename = void>
    struct foo : std::integral_constant<int, -1> {};
    
    template <int N>
    struct foo<N, typename std::enable_if<(0<=N)&&(N<10)>::type> : std::integral_constant<int, N> {};
    
    #include <iostream>
    int main()
    {
        std::cout << foo<-1>::value << '\n';
        std::cout << foo<0>::value << '\n';
        std::cout << foo<1>::value << '\n';
        std::cout << foo<9>::value << '\n';
        std::cout << foo<10>::value << '\n';
    }
    

    ideone
    Alternativ kann man nat. z.B. auch einfach per std::conditional eine passende Basisklasse auswählen.

    (Ich habe, glaube ich, vor einiger Zeit mal etwas ausführlicher den Mechanismus bei partieller Spezialisierung beschrieben.)



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


Anmelden zum Antworten