SFINAE - IsExplicitConvertible und IsDefaultConstructible
-
Hallo,
also zunächst mal würde ich gerne wissen, ob es allgemein korrekt ist (nach allen drei mehr oder weniger aktuellen Standards) einen Laufzeitausdruck auf Richtigkeit zu prüfen, indem man das folgende Konstrukt verwendet:
template<typename T> static NoType Check(...); template<typename T> static YesType Check(SizeToType<sizeof(Ausdruck)>*);Denn seltsamerweise funktionieren solche Konstrukte teilweise, teilweise hingegen auch nicht. Hier knüpft auch meine nächste Frage gerade an. Ist das Folgende korrekt?
template<typename From, typename To> class IsExplicitConvertible { template<typename FromT, typename ToT> static NoType Check(...); template<typename FromT, typename ToT> static YesType Check(SizeToType<sizeof(static_cast<ToT>(DeclVal<FromT>()))>*); public: static const bool Value = sizeof(Check<From, To>(0)) == sizeof(YesType); };Zur Erklärung kurz falls die Bezeichner nicht selbsterklärend genug sind:
SizeToTypeist etwa dasselbe wietemplate<std::size_t Size> using SizeToType = std::integral_constant<std::size_t, Size>;NoTypeundYesTypesind garantiert unterschiedich grosse Typen
Der Code zu
IsExplicitConvertiblefunktioniert auf MSVC nicht, auf GCC 4.8.1 hingegen schon. Wäre er also eigentlich korrekt?Nun zu einem Code, der sowohl auf MSVC als auch auf GCC nicht das gewünschte Ergebnis liefert:
template<typename T> class IsDefaultConstructible { template<typename U> static NoType Check(...); template<typename U> static YesType Check(SizeToType<sizeof(U())>*); public: static const bool Value = sizeof(Check<T>(0)) == sizeof(YesType); };Interessant ist jedoch, dass dieser Code bei MSVC bei allen Typen in
trueresultiert, bei GCC jedoch infalse. Wäre der Code korrekt?Hinzuzufügen ist vielleicht noch, dass bei MSVC die meisten Klassen aus
<type_traits>mit Intrinsics implementiert wurden.Gruss.
Edit:
Hier ist das Minimalbeispiel:#include <iostream> typedef std::size_t SizeType; template<typename T> struct Identity { typedef T Type; }; typedef Identity<char(&)[1]>::Type NoType; typedef Identity<char(&)[2]>::Type YesType; template<typename T, T Val> struct IntegralConstant : public Identity<IntegralConstant<T, Val> > { typedef T ValueType; #ifdef Lib_Cpp11 static constexpr ValueType Value = Val; constexpr operator ValueType() const { return this->Value; } #else // Lib_Cpp11 static const ValueType Value = Val; operator ValueType() const { return this->Value; } #endif // Lib_Cpp11 }; template<SizeType Val> struct SizeToType : public IntegralConstant<SizeType, Val> { }; template<bool Val> struct BoolToType : public IntegralConstant<bool, Val> { }; template<typename T> T& DeclVal(); namespace Detail { template<typename From, typename To> class IsExplicitConvertible { template<typename FromT, typename ToT> static NoType Check(...); template<typename FromT, typename ToT> static YesType Check(SizeToType<sizeof(static_cast<ToT>(DeclVal<FromT>()))>*); public: static const bool Value = sizeof(Check<From, To>(0)) == sizeof(YesType); }; template<typename T> class IsDefaultConstructible { template<typename U> static NoType Check(...); template<typename U> static YesType Check(SizeToType<sizeof(U())>*); public: static const bool Value = sizeof(Check<T>(0)) == sizeof(YesType); }; } template<typename From, typename To> struct IsExplicitConvertible : public BoolToType<Detail::IsExplicitConvertible<From, To>::Value> { }; template<typename T> struct IsDefaultConstructible : public BoolToType<Detail::IsDefaultConstructible<T>::Value> { }; struct Foo1 { explicit Foo1(int); operator char(); }; class Foo2 { Foo2(); }; struct Foo3 { }; int main() { std::cout << IsExplicitConvertible<int, float>::Value << '\n'; // Sollte 1 sein std::cout << IsExplicitConvertible<int, Foo1>::Value << '\n'; // Sollte 1 sein std::cout << IsExplicitConvertible<float, Foo1>::Value << '\n'; // Sollte 1 sein std::cout << IsExplicitConvertible<Foo1, char>::Value << '\n'; // Sollte 1 sein std::cout << IsExplicitConvertible<Foo1, double>::Value << '\n'; // Sollte 1 sein std::cout << IsExplicitConvertible<bool, Foo2>::Value << '\n'; // Sollte 0 sein std::cout << IsExplicitConvertible<int, void*>::Value << '\n'; // Sollte 0 sein std::cout << '\n'; std::cout << IsDefaultConstructible<int>::Value << '\n'; // Sollte 1 sein std::cout << IsDefaultConstructible<Foo1>::Value << '\n'; // Sollte 0 sein std::cout << IsDefaultConstructible<Foo2>::Value << '\n'; // Sollte 0 sein std::cout << IsDefaultConstructible<Foo3>::Value << '\n'; // Sollte 1 sein }Edit 2:
Den Blödsinn rausgelöscht.voidist natürlich nicht in Default-Konstruierbare Typen konvertierbar. Ich dachte, dass das mit dem Übergeben einesvoids (wie bei Funktionen) auch bei Konstruktoren funktioniert, dem ist aber nicht so.
-
Mir fehlt das lauffähige Programm zum Testen.
-
Wäre er also eigentlich korrekt?
Er ist korrekt.
Wäre der Code korrekt?
Nein, der zweite Code ist ganz subtil falsch.
sizeof(U())U()ist ein Funktionstyp. Und man darfsizeofnicht auf Funktionstypen anwenden.§5.3.3/1 schrieb:
The
sizeofoperator shall not be applied to an expression that has function or incomplete typeDaher wird die Instantiierung des zweiten Funktionstemplates immer in einem deduction failure enden.
-
gcc hat übrigens den sutilen fehler angewarnt.
"invalid application of 'sizeof' to a function type"Und dann kapiert gcc Folgendes:
static YesType Check(SizeToType<sizeof((U()))>*);
-
gcc hat übrigens den sutilen fehler angewarnt.
Kann ich nicht nachvollziehen. Welcher Code?
-
volkard schrieb:
Mir fehlt das lauffähige Programm zum Testen.
Ups, sorry. Ich hab's ergänzt.
Arcoth schrieb:
U()ist ein Funktionstyp.
Wow, das ist gemein.volkard schrieb:
Und dann kapiert gcc Folgendes:
Funktioniert das, weil man Typen nicht in ein Klammerpaar setzen darf, Werte aber schon? Cooler Trick.
-
Arcoth schrieb:
gcc hat übrigens den sutilen fehler angewarnt.
Kann ich nicht nachvollziehen. Welcher Code?
cout<<sizeof(Foo1())<<'\n';