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 TypenU
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<>
undis_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 sagtis_convertible<>
Nein undis_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 mitstatic_cast
natürlich noch Downcasts machen. Und du kannst alles was du willst nachvoid
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 TypenU
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.