C++11: type-safe printf



  • 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. 😮 👍



  • 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.


Anmelden zum Antworten