c++ boost enable_if
-
Hi,
Ich raff diese enable_if Geschichte nicht so richtig.Ich habe eine Templateklasse mit einem Dimensionsparameter.
template< typename TYPE, int DIM > class TPoint{ public: // operator [] private: std::array<TYPE, DIM> m_data; }Als Komfortfeature würde ich gern für Dimensionen 2 und 3 entsprechende Memberfunktionen zum lesen und schreiben von x,y,z einfügen, die aber nur bei DIM 2 oder 3 aktiv sein sollen.
TYPE getX() const; //und void setX( TYPE newx );Anstelle von Spezialisierung und der damit verbundenen Codeduplizierung wollte ich gerne das enable_if benutzen.
Aber ich weiß nicht, wie ich es aufschreiben soll.
Ich hab folgende Traits definiert:
template <int DIM> struct is_dimGte2 { static const bool value = false; }; template < > struct is_dimGte2<2>{ static const bool value = true; }; template < > struct is_dimGte2<3>{ static const bool value = true; }; template <int DIM> struct is_dimGte3 { static const bool value = false; }; template < > struct is_dimGte3<3>{ static const bool value = true; };und so die funktion deklariert
typename boost::enable_if_c<is_dimGte2<DIM>::value, TYPE>::type getY() { return data[2]; }jetzt kann ich aber nur noch TPoint<2> und TPoint<3> erzeugen
kann mir vielleicht jemand einen Tip geben?
-
vlad_tepesch schrieb:
und so die funktion deklariert
typename boost::enable_if_c<is_dimGte2<DIM>::value, TYPE>::type getY() { return data[2]; }SFINAE bezieht sich auf Funktionstemplates, nicht auf normale Funktionen. Auch dann nicht, wenn diese Funktionen zufällig Member einer Templateklasse sind.
-
*Klassentemplate :p
-
Ich würde das per CRTP lösen, etwa so:
template<typename Type, typename ValueType, unsigned DIM> class TPointImpl { }; template<typename Type, typename ValueType> class TPointImpl<Type, ValueType, 2> { public: void setX(ValueType val) { static_cast<Type*>(this)->set(0, val); } void setY(ValueType val) { static_cast<Type*>(this)->set(1, val); } }; template<typename T, unsigned DIM> class TPoint : public TPointImpl<TPoint<T,DIM>, T, DIM> { public: typedef T value_type; private: T data[DIM]; public: void set(unsigned dim, value_type val) { data[dim]=val; } }; int main() { TPoint<int, 2> p; p.setX(7); }
-
Ich stand neulich vor genau demselben Problem, auch für eine point-Klasse
Ich hab's dann in etwa so gelöst, wobei mich aber auch alternative Vorschläge interessieren würden.enum no_init_tag {no_init}; template<class T, int Dim, class Derived> struct point_base : std::array<T,Dim> { ..... point_base() { std::fill(this->begin(),this->end(),T(0)); } explicit point_base(no_init_tag) {} Derived& operator+=(Derived const& x) { blas::axpy(Dim,1,x.begin(),this->begin()); return self(); } ..... Alles möglich andere noch ..... protected: Derived & self() {return static_cast<Derived &>(*this);} Derived const& self() const {return static_cast<Derived const&>(*this);} }; template<class T, int Dim> struct point : point_base<T,Dim,point<T,Dim> > {}; template<class T> struct point<T,2> : point_base<T,2,point<T,2> > { typedef point_base<T,2,point<T,2> > base_type; point() {} point(T x, T y) : base_type(no_init) { (*this)[0] = x; (*this)[1] = y; } void setX(T x){(*this)[0]=x;} void setY(T y){(*this)[1]=y;} };Also: Der gemeinsame Kram ist in point_base drin und die speziellen Konstruktoren gibt's dann in der Spezialisierung einer abgeleiteten Klasse.
So richtig dolle gefällt mir dieser Workaround nicht. Aber was viel besseres fällt mir auch nicht ein. Mit 'nem variadischen Konstruktortemplate könnte man es ja auch regeln, allerdings unterstützt das der Compiler von Microsoft noch nicht.
-
Shade Of Mine schrieb:
Ich würde das per CRTP lösen, etwa so:
Danke, das sieht super aus.
Das einzige was mich daran stört, ist, dass man den namespace mit internen Typen vollmüllt.
-
vlad_tepesch schrieb:
Shade Of Mine schrieb:
Ich würde das per CRTP lösen, etwa so:
Danke, das sieht super aus.
Das einzige was mich daran stört, ist, dass man den namespace mit internen Typen vollmüllt.Du kannst *Impl ja auch in einen anderen Namespace stecken.
-
Da in C++11 auch die Templateparameter von Funktionstemplates Defaultargumente haben dürfen, kann im Prinzip jede Funktion in ein Template umgewandelt werden
template <bool a, bool b> struct and_ : std::integral_constant<bool, a && b> {}; template <typename T, size_t N> class point { public: template <typename U = void, typename std::enable_if<and_<N==2||N==3, std::is_same<U,void>::value>::value, int>::type = 0> const T& get_x() const { return (*this)[0]; } template <typename U = void, typename std::enable_if<and_<N==2||N==3, std::is_same<U,void>::value>::value, int>::type = 0> const T& get_y() const { return (*this)[1]; } template <typename U = void, typename std::enable_if<and_<N==3, std::is_same<U,void>::value>::value, int>::type = 0> const T& get_z() const { return (*this)[2]; } const T& operator[](size_t i) const { return m_data[i]; } // ... private: std::array<T, N> m_data; };
-
camper schrieb:
template <bool a, bool b> struct and_ : std::integral_constant<bool, a && b> {}; template <typename T, size_t N> class point { public: template <typename U = void, typename std::enable_if<and_<N==2||N==3, std::is_same<U,void>::value>::value, int>::type = 0> const T& get_x() const { return (*this)[0]; } template <typename U = void, typename std::enable_if<and_<N==2||N==3, std::is_same<U,void>::value>::value, int>::type = 0> const T& get_y() const { return (*this)[1]; } template <typename U = void, typename std::enable_if<and_<N==3, std::is_same<U,void>::value>::value, int>::type = 0> const T& get_z() const { return (*this)[2]; } const T& operator[](size_t i) const { return m_data[i]; } // ... private: std::array<T, N> m_data; };Muss das so kompliziert? Was spricht denn gegen
template <typename T, size_t N> class point { public: template <class = typename std::enable_if<(N==2||N==3)>::type> const T& get_x() const { return (*this)[0]; } template <class = typename std::enable_if<(N==2||N==3)>::type> const T& get_y() const { return (*this)[1]; } template <class = typename std::enable_if<( N==3)>::type> const T& get_z() const { return (*this)[2]; } const T& operator[](size_t i) const { return m_data[i]; } // ... private: std::array<T, N> m_data; };?
Edit: Gut, das schützt nicht wirklich davor, dass jemand get_x<void>() schreibt. Insofern ist das, was du gezeigt hast, "idiotensicher" -- wobei das and_ aber trotzdem überflüssug aussieht.
-
-
krümelkacker schrieb:
Muss das so kompliziert? Was spricht denn gegen
template <typename T, size_t N> class point { public: template <class = typename std::enable_if<(N==2||N==3)>::type> const T& get_x() const { return (*this)[0]; } template <class = typename std::enable_if<(N==2||N==3)>::type> const T& get_y() const { return (*this)[1]; } template <class = typename std::enable_if<( N==3)>::type> const T& get_z() const { return (*this)[2]; } const T& operator[](size_t i) const { return m_data[i]; } // ... private: std::array<T, N> m_data; };Das ist ill-formed für alle N!=3. Bei der Instantiierung von point<T,N> werden auch alle Memberdeklarationen instantiiert. Da
typename std::enable_if<(N==2||N==3)>::typenicht von einem Templateparamter des Funktionstemplates selbst abhängt, wird der Bezeichner an dieser Stelle gebunden. Und weil es sich dabei nicht um einen Typ handelt (enable_if<false>::type existiert ja nicht), ist die Deklaration ill-formed. Und weil es hier nicht um die Zusammenstellung eines Überladungssets geht, greift SFINAE nicht.
and_ mag in diesem Zusammenhang übertrieben sein
template <bool b = true, typename std::enable_if<(N==2||N==3)&&b, int>::type = 0> const T& get_x() const { return (*this)[0]; }funktioniert wahrscheinlich auch. Ganz sicher bin ich mir aber nicht (14.6/8).
Edit:
template <bool enable = N==2||N==3, typename std::enable_if<enable, int>::type = 0> const T& get_x() const { return (*this)[0]; }ist simpel und problemlos. Ganz nebenbei kann man die Funktion auch für andere N explizit benutzen, was zumindest in diesem Fall nicht unbedingt sinnlos ist.
-

danke für den beitrag!