Gibt es einen Type-Trait, der zwischen "castability" und "constructibility" unterscheidet?



  • Frage im Titel. Ein Beispiel:

    #include <type_traits>
    
    static int i = 42;
    static int* ip = &i;
    static void* vp = ip;
    
        // Implizit von `void*` nach `int*` geht nicht.
    static_assert(!std::is_convertible_v<void*, int*>);
    //static int* ip2{ vp };
    
        // Explizit von `void*` nach `int*` geht auch nicht.
    static_assert(!std::is_constructible_v<int*, void*>);
    //static int* ip2(vp);
    
        // Mit Cast geht es aber.
    static int* ip3 = static_cast<int*>(vp);
    

    Klar, man könnte sich selbst einen definieren, aber vielleicht gibts das schon? Und gibt es denn außer void* noch Datentypen, bei denen zwischen "constructibility" und "castability" ein Unterschied besteht?

    Und kann ich einen eigenen Datentypen T so definieren, dass für einen bestimmten Typen U die direkte Konstruktion (T(U{ })) nicht erlaubt ist, aber ein Cast (static_cast<T>(U{ })) schon?



  • @dtm sagte in Gibt es einen Type-Trait, der zwischen "castability" und "constructibility" unterscheidet?:

    Und gibt es denn außer void* noch Datentypen, bei denen zwischen "constructibility" und "castability" ein Unterschied besteht?

    Meinst du sowas:

    struct T
    {
    };
    
    struct U
    {
        operator T () const { return T{};}
    };
    
    
    int main() {
        static_assert(std::is_convertible_v<U, T>); // ok
        
        static_assert(std::is_constructible_v<U, T>); // error
    
        return 0;
    }
    


  • Die Typparameter müssen bei is_convertible<> und is_constructible<> umgekehrt verwendet werden. Wenn du das machst, siehst du bei deinem implicit conversion operator keinen Unterschied mehr:

        static_assert(std::is_convertible_v<U, T>); // ok
        static_assert(std::is_constructible_v<T, U>); // ok
    

    Du siehst einen Unterschied, wenn du den Operator explicit machst: dann sagt is_convertible<> Nein und is_constructible<> Ja.

    Beides betrifft aber nicht meine Frage.



  • Klar, man könnte sich selbst einen definieren, aber vielleicht gibts das schon?

    Ich kenne nichts fertiges.

    Und gibt es denn außer void* noch Datentypen, bei denen zwischen "constructibility" und "castability" ein Unterschied besteht?

    Ja, klar. Erstmal natürlich die cv-varianten von void*. Dann kannst du mit static_cast natürlich noch Downcasts machen. Und du kannst alles was du willst nach void casten.

    Beispiel:

    #include <utility>
    
    namespace detail {
    
    template <typename, typename, typename>
    struct is_static_castable_impl {
        static constexpr bool value = false;
    };
    
    template <typename From, typename To>
    struct is_static_castable_impl<From, To, decltype(static_cast<To>(std::declval<From>()), 0)> {
        static constexpr bool value = true;
    };
    
    } // namespace detail
    
    template <typename From, typename To>
    using is_static_castable = detail::is_static_castable_impl<From, To, int>;
    
    template <typename From, typename To>
    constexpr bool is_static_castable_v = is_static_castable<From, To>::value;
    
    // ----
    
    #include <iomanip>
    #include <iostream>
    #include <type_traits>
    
    struct Base {};
    struct Der : Base {};
    
    template <typename From, typename To>
    void test() {
        std::cout << std::boolalpha << std::left;
        std::cout
            << std::setw(6) << typeid(From).name() << " -> "
            << std::setw(6) << typeid(To).name()
            << "  cast " << std::setw(5) <<is_static_castable_v<From, To>
            << "  conv " << std::setw(5) <<std::is_convertible_v<From, To>
            << "\n";
    }
    
    int main() {
        test<int*, void*>();
        test<void*, int*>();
        test<int, void>();
        test<Der*, Base*>();
        test<Base*, Der*>();
        test<int*, long*>();
    }
    
    /*
    
    Pi     -> Pv      cast true   conv true 
    Pv     -> Pi      cast true   conv false
    i      -> v       cast true   conv false
    P3Der  -> P4Base  cast true   conv true 
    P4Base -> P3Der   cast true   conv false
    Pi     -> Pl      cast false  conv false
    
    */
    

    Und kann ich einen eigenen Datentypen T so definieren, dass für einen bestimmten Typen U die direkte Konstruktion (T(U{ })) nicht erlaubt ist, aber ein Cast (static_cast<T>(U{ })) schon?

    Soweit ich weiss nicht.



  • Danke für die ausführliche Antwort!

    Dass es keine Möglichkeit gibt, diesen feinen Unterschied für eigene Typen nachzubilden, ist wohl der Grund für die Existenz von static_pointer_cast<>() und dergleichen. Unschön, aber es hilft ja nichts.


Anmelden zum Antworten