Prüfen, ob eine Klasse eine Instanziierung eines Templates ist



  • Hi zusammen,

    ich möchte über template Metaprogramming und type_traits prüfen, ob eine Klasse eine Instanziierung eines bestimmten templates ist. Leider übersteigt mein Vorhaben meine Kenntnisse, kann mir hier jemand auf die Sprünge helfen?

    Hier mein bisher bester Versuch, basierend auf diesem Ansatz.

    #include <type_traits>
    
    template<std::int64_t num, std::int64_t den>
    struct Base
    {
        static constexpr std::int64_t Num = num;
        static constexpr std::int64_t Den = den;
    };
    
    template<std::int64_t num, std::int64_t den>
    struct Derived1 : Base<num,den>
    {
    };
    
    template<std::int64_t num, std::int64_t den>
    struct Derived2 : Base<num,den>
    {
    };
    
    template<typename T, typename U>
    static constexpr bool is_specialisation_of_v = std::false_type{};
    
    template<template<std::int64_t, std::int64_t> typename T,
             std::int64_t n, std::int64_t d,
             template<std::int64_t, std::int64_t> typename U>
    static constexpr bool is_specialisation_of_v<T<n,d>, U> = std::true_type{};
    
    int main()
    {
        using t1 = Derived1<1,1>;
        using t2 = Derived2<1,1>;
         
        bool const b1 = is_specialisation_of_v<t1,Derived1>;
    }
    

    Hier die dazugehörigen Fehlermeldungen:

    prog.cc:27:54: error: use of template template parameter 'U' requires template arguments
       27 | static constexpr bool is_specialisation_of_v<T<n,d>, U> = std::true_type{};
          |                                                      ^
    prog.cc:26:56: note: template is declared here
       26 |          template<std::int64_t, std::int64_t> typename U>
          |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~          ^
    prog.cc:34:47: error: use of class template 'Derived1' requires template arguments
       34 |     bool const b1 = is_specialisation_of_v<t1,Derived1>;
          |                                               ^
    prog.cc:12:8: note: template is declared here
       11 | template<std::int64_t num, std::int64_t den>
          | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       12 | struct Derived1 : Base<num,den>
          |        ^
    2 errors generated.
    


  • Das Problem ist, dass deine Klassen-Templates auf numerischen Konstanten basieren (nicht auf anderen Klassen/Typen).
    Dieselbe Fehlermeldung gibt es auch (bezogen auf dem Code aus dem Artikel) wenn man std::array verwendet: C++ Compiler Explorer Code

    std::array<int, 10> y;
    std::cerr << is_instance_of_v<decltype(y),std::array> << ' ';  // true
    

    Man muß also dann bei deinem Code immer konkrete Instanziierungen vergleichen, z.B.

    bool const b1 = is_specialisation_of_v<t1,Derived1<1,1>>;
    

    Der Code dazu sähe dann so aus: C++ Compiler Explorer Code

    Nun wird nur Derived1<1,1> als "gleich" angesehen, nicht andere Instanziierungen wie Derived1<1,2>.
    Ich weiß aber nicht, ob du das so haben willst?

    Edit: Das wäre ja einfach typeof(x) == typeof(y) - LOL.
    Hier die abgeänderte Version, so daß jede Instanziierung von Derived1 als gleich angesehen wird: C++ Compiler Explorer Code

    template<template<std::int64_t, std::int64_t> typename T,
             std::int64_t n1, std::int64_t d1,
             std::int64_t n2, std::int64_t d2>
    static constexpr bool is_specialisation_of_v<T<n1,d1>, T<n2,d2>> = std::true_type{};
    


  • @Th69 sagte in Prüfen, ob eine Klasse eine Instanziierung eines Templates ist:

    Man muß also dann bei deinem Code immer konkrete Instanziierungen vergleichen

    Könnte man dann auch std::is_base_of_v verwenden?



  • Auch da funktioniert es nicht mit numerischen Template-Parametern: C++ Compiler Explorer Code

    std::array<int, 10> y;
    std::cerr << std::is_base_of_v<decltype(y),std::array>;
    

    ergibt auch dieselbe Fehlermeldung:

    <source>: In function 'int main()':
    <source>:35:56: error: type/value mismatch at argument 2 in template parameter list for 'template<class _Base, class _Derived> constexpr const bool std::is_base_of_v<_Base, _Derived>'
       35 |   std::cerr << std::is_base_of_v<decltype(y),std::array>
          |                                                        ^
    <source>:35:56: note:   expected a type, got 'array'
    

    Mich würde aber auch interessieren, ob dies generell überhaupt möglich ist (oder ob dies noch ein Defect im Standard ist).



  • Ein Problem ist, dass der Standard nichts darüber aussagt, ob und wann templates als "gleich" angesehen werden. D.h. ein reiner Template Vergleich wird nicht funktionieren.
    Der Trick, den ich nutzen würde, ist eine konkrete Template-Instanz mit einem Template zu vergleichen, indem alle Argumente aus ersterem auch an zweiteres übergeben werden.

    template<typename T,
             template<auto...> typename U>
    constexpr bool is_specialisation_of_v = false;
    
    template<template<auto...> typename T,
             auto... args,
             template<auto...> typename U>
    constexpr bool is_specialisation_of_v<T<args...>, U>  = std::is_base_of_v<U<args...>, T<args...>>; // oder is_same_v, je nachdem, was du erreichen willst
    

    Das Mischen von Template-Argumenten und NTTP ist so leider nicht möglich und müsste für den konkreten Fall behandelt werden.
    https://godbolt.org/z/ecWocY8Kn

    EDIT: Auf ähnliche Art und Weise, habe ich für mimic++ übrigens die "Default-Parameter-Detection" implementiert:
    https://github.com/DNKpp/mimicpp/blob/e4de0a5631ca07062f23cb89bcb429529e529fb3/include/mimic%2B%2B/printing/type/Templated.hpp#L50

        template <typename Type, template <typename...> typename Template, typename LeadingArgList>
        struct is_default_arg_for
            : public std::false_type
        {
        };
    
        template <typename Type, template <typename...> typename Template, typename... LeadingArgs>
            requires requires { typename Template<LeadingArgs...>; }
        struct is_default_arg_for<Type, Template, util::type_list<LeadingArgs...>>
            : public std::bool_constant<
                  std::same_as<
                      Template<LeadingArgs...>,
                      Template<LeadingArgs..., Type>>>
        {
        };
    

    Das erkennt dann, dass z.B. std::allocator<int> das default-argument von std::vector<int, std::allocator<int>> ist.