From ecfde0a1c0933d580b43c2250de1e459feb5df88 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 14 Dec 2023 12:42:58 +0000 Subject: [PATCH 1/4] Working code for more flexibility with GraphVisuals, but would prefer type traits solution --- examples/graph2.cpp | 18 +++++++++-------- morph/GraphVisual.h | 42 +++++++++++++++++++++++++++------------ morph/expression_sfinae.h | 10 ++++++++++ morph/vvec.h | 28 ++++++++++++++++++++------ 4 files changed, 71 insertions(+), 27 deletions(-) diff --git a/examples/graph2.cpp b/examples/graph2.cpp index a178f33a..075f6b19 100644 --- a/examples/graph2.cpp +++ b/examples/graph2.cpp @@ -31,8 +31,10 @@ int main() static constexpr bool setup_axes = true; try { - morph::vvec absc = {-.5, -.4, -.3, -.2, -.1, 0, .1, .2, .3, .4, .5, .6, .7, .8}; - morph::vvec data = absc.pow(3); + morph::vvec _absc = {-.5, -.4, -.3, -.2, -.1, 0, .1, .2, .3, .4, .5, .6, .7, .8}; + morph::vvec data = _absc.pow(3); + std::deque absc (_absc.size()); + std::copy (_absc.begin(), _absc.end(), absc.begin()); auto gvup = std::make_unique> (morph::vec({0,0,0})); v.bindmodel (gvup); @@ -55,16 +57,16 @@ int main() gvup->setdata (absc, data, ds); ds.markerstyle = morph::markerstyle::square; ds.setcolour ({0.0, 1.0, 0.0}); - gvup->setdata (absc, absc.pow(4), ds); + gvup->setdata (absc, _absc.pow(4), ds); } else { gvup->policy = morph::stylepolicy::allcolour; // markers, lines, both, allcolour // The code here demonstrates how to include unicode characters (ss2 is "superscript 2") using morph::unicode; gvup->setdata (absc, absc, "y=x"); - gvup->setdata (absc, absc.pow(2)+0.05f, "y=x" + unicode::toUtf8(unicode::ss2)); - gvup->setdata (absc, absc.pow(3)+0.1f, "y=x" + unicode::toUtf8(unicode::ss3)); - gvup->setdata (absc, absc.pow(4)+0.15f, "y=x" + unicode::toUtf8(unicode::ss4)); - gvup->setdata (absc, absc.pow(5)+0.2f, "y=x" + unicode::toUtf8(unicode::ss5)); + gvup->setdata (absc, _absc.pow(2)+0.05f, "y=x" + unicode::toUtf8(unicode::ss2)); + gvup->setdata (absc, _absc.pow(3)+0.1f, "y=x" + unicode::toUtf8(unicode::ss3)); + gvup->setdata (absc, _absc.pow(4)+0.15f, "y=x" + unicode::toUtf8(unicode::ss4)); + gvup->setdata (absc, _absc.pow(5)+0.2f, "y=x" + unicode::toUtf8(unicode::ss5)); } if constexpr (setup_axes) { @@ -86,7 +88,7 @@ int main() v.waitevents (0.018); // Don't update this fast. That's crazy! if ((rcount++)%20 == 0) { - gv->update (absc, absc.pow(2)*addn, 1); + gv->update (absc, _absc.pow(2)*addn, 1); addn += 0.2f; } // want gv->update (datasets); // to update all at once. THEN I'm done. diff --git a/morph/GraphVisual.h b/morph/GraphVisual.h index ac4e2c59..6c1abe7c 100644 --- a/morph/GraphVisual.h +++ b/morph/GraphVisual.h @@ -327,6 +327,7 @@ namespace morph { this->ord2_scale.reset(); this->setlimits_x (this->datamin_x, this->datamax_x*2.0f); if (!this->ord1.empty()) { + // vvec, vvec, datasetstyle this->setdata (this->absc1, this->ord1, this->ds_ord1); } if (!this->ord2.empty()) { @@ -366,8 +367,11 @@ namespace morph { } //! Update the data for the graph, recomputing the vertices when done. - void update (const std::vector& _abscissae, - const std::vector& _data, const size_t data_idx) + template < template typename Ctnr1, + template typename Ctnr2, + typename Alctr=std::allocator > + void update (const Ctnr1& _abscissae, + const Ctnr2& _data, const size_t data_idx) { size_t dsize = _data.size(); @@ -386,9 +390,10 @@ namespace morph { // May need a re-autoscaling option somewhere in here. // Transfor the data into temporary containers sd and ad - std::vector sd (dsize, Flt{0}); - std::vector ad (dsize, Flt{0}); + Ctnr2 sd (dsize, Flt{0}); this->ord1_scale.transform (_data, sd); + + Ctnr1 ad (dsize, Flt{0}); this->abscissa_scale.transform (_abscissae, ad); // Now sd and ad can be used to construct dataCoords x/y. They are used to @@ -416,8 +421,11 @@ namespace morph { } //! update() overload that allows you also to set the data label - void update (const std::vector& _abscissae, - const std::vector& _data, std::string datalabel, const size_t data_idx) + template < template typename Container, + typename T, + typename Allocator=std::allocator > + void update (const Container& _abscissae, + const Container& _data, std::string datalabel, const size_t data_idx) { if (data_idx >= this->datastyles.size()) { std::cout << "Can't add change data label at graphDataCoords index " << data_idx << std::endl; @@ -461,7 +469,10 @@ namespace morph { //! Set a dataset into the graph using default styles, incrementing colour and //! marker shape as more datasets are included in the graph. - void setdata (const std::vector& _abscissae, const std::vector& _data, + template < template typename Ctnr1, + template typename Ctnr2, + typename Alctr=std::allocator > + void setdata (const Ctnr1& _abscissae, const Ctnr2& _data, const std::string name = "", const morph::axisside axisside = morph::axisside::left) { DatasetStyle ds(this->policy); @@ -469,7 +480,7 @@ namespace morph { if (!name.empty()) { ds.datalabel = name; } size_t data_index = this->graphDataCoords.size(); this->setstyle (ds, DatasetStyle::datacolour(data_index), DatasetStyle::datamarkerstyle (data_index)); - this->setdata (_abscissae, _data, ds); + this->setdata (_abscissae, _data, ds); } //! setdata overload that accepts vvec of coords (as morph::vec) @@ -489,8 +500,10 @@ namespace morph { //! Set a dataset into the graph. Provide abscissa and ordinate and a dataset //! style. The locations of the markers for each dataset are computed and stored //! in this->graohDataCoords, one vector for each dataset. - void setdata (const std::vector& _abscissae, - const std::vector& _data, const DatasetStyle& ds) + template < template typename Ctnr1, + template typename Ctnr2, + typename Alctr=std::allocator > + void setdata (const Ctnr1& _abscissae, const Ctnr2& _data, const DatasetStyle& ds) { if (_abscissae.size() != _data.size()) { std::stringstream ee; @@ -531,8 +544,8 @@ namespace morph { if (dsize > 0) { // Transform the data into temporary containers sd and ad - std::vector sd (dsize, Flt{0}); - std::vector ad (dsize, Flt{0}); + Ctnr1 ad (dsize, Flt{0}); + Ctnr2 sd (dsize, Flt{0}); if (ds.axisside == morph::axisside::left) { this->ord1_scale.transform (_data, sd); } else { @@ -600,7 +613,10 @@ namespace morph { protected: //! Compute the scaling of ord1_scale and abscissa_scale according to the scalingpolicies - void compute_scaling (const std::vector& _abscissae, const std::vector& _data, const morph::axisside axisside) + template < template typename Ctnr1, + template typename Ctnr2, + typename Alctr=std::allocator > + void compute_scaling (const Ctnr1& _abscissae, const Ctnr2& _data, const morph::axisside axisside) { morph::range data_maxmin = morph::MathAlgo::maxmin (_data); morph::range absc_maxmin = morph::MathAlgo::maxmin (_abscissae); diff --git a/morph/expression_sfinae.h b/morph/expression_sfinae.h index 0f0e499c..79632bc9 100644 --- a/morph/expression_sfinae.h +++ b/morph/expression_sfinae.h @@ -95,4 +95,14 @@ namespace morph { static constexpr bool value = std::is_same(0)),std::true_type>::value; }; + // Test for the has a const_iterator trait + template + class has_const_iterator + { + template static char test(typename C::const_iterator*); + template static int test(...); + public: + enum { value = sizeof(test(0)) == sizeof(char) }; + }; + } // morph:: diff --git a/morph/vvec.h b/morph/vvec.h index 47f1ca66..a887d770 100644 --- a/morph/vvec.h +++ b/morph/vvec.h @@ -21,6 +21,7 @@ #include #include #include +#include namespace morph { @@ -76,14 +77,19 @@ namespace morph { //! \return the fourth component of the vector S w() const { return (*this)[3]; } - //! Set data members from an std::vector (by copying) - template - void set_from (const std::vector<_S>& vec) + //! Set data members from a templated container (by copying). Containers like + //! std::vector, std::deque and so on should work, but std::array, std::map won't. + // FIXME: Implement traits solution? https://stackoverflow.com/questions/7728478/c-template-class-function-with-arbitrary-container-type-how-to-define-it + template < template typename Ctnr, + typename _S=S, + typename Alctr=std::allocator<_S> > + void set_from (const Ctnr<_S, Alctr>& contained) { - this->resize(vec.size()); - std::copy (vec.begin(), vec.end(), this->begin()); + this->resize (contained.size()); + std::copy (contained.begin(), contained.end(), this->begin()); } +#if 1 //! Set data members from an std::array, matching the size of the array first. template void set_from (const std::array<_S, N>& ar) @@ -91,8 +97,18 @@ namespace morph { this->resize(N); std::copy (ar.begin(), ar.end(), this->begin()); } +#else + //! Traits set_from that could work with std::array? + template + std::enable_if_t::value, void> // Need more tests - satisfy LegacyInputIterator + set_from (const Container& c) + { + this->resize (c.size()); + std::copy (c.begin(), c.end(), this->begin()); + } +#endif - //! Set all elements from the value type v. Same as vvec::set + //! Set all elements from the value type v. template void set_from (const _S& v) { std::fill (this->begin(), this->end(), v); } From 84b0456e903d3d52176b3c34a258ec5779d807a0 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 14 Dec 2023 17:04:56 +0000 Subject: [PATCH 2/4] Good progress --- examples/graph3.cpp | 4 +- morph/CMakeLists.txt | 2 +- morph/GraphVisual.h | 52 +++++++++--------- morph/MathAlgo.h | 12 ++--- morph/MathImpl.h | 16 +++--- morph/Scale.h | 39 ++++++-------- morph/Winder.h | 2 +- morph/{expression_sfinae.h => trait_tests.h} | 44 ++++++++++----- morph/vvec.h | 34 +++--------- tests/CMakeLists.txt | 6 +++ tests/test_trait_tests.cpp | 42 +++++++++++++++ tests/testvvec_set_from.cpp | 56 ++++++++++++++++++++ 12 files changed, 202 insertions(+), 107 deletions(-) rename morph/{expression_sfinae.h => trait_tests.h} (62%) create mode 100644 tests/test_trait_tests.cpp create mode 100644 tests/testvvec_set_from.cpp diff --git a/examples/graph3.cpp b/examples/graph3.cpp index efe70784..f681ad16 100644 --- a/examples/graph3.cpp +++ b/examples/graph3.cpp @@ -32,12 +32,14 @@ int main() auto gv = std::make_unique>(morph::vec({0,0,0})); v.bindmodel (gv); morph::vvec data = absc.pow(3); + morph::vec ardata; + ardata.set_from (static_cast>(data)); ds.linecolour = {1.0, 0.0, 0.0}; ds.linewidth = 0.015f; ds.markerstyle = morph::markerstyle::triangle; ds.markercolour = {0.0, 0.0, 1.0}; - gv->setdata (absc, data, ds); + gv->setdata (absc, ardata, ds); gv->axisstyle = morph::axisstyle::L; diff --git a/morph/CMakeLists.txt b/morph/CMakeLists.txt index 2a445eff..a74e4588 100644 --- a/morph/CMakeLists.txt +++ b/morph/CMakeLists.txt @@ -4,7 +4,7 @@ # Header installation install( - FILES Quaternion.h tools.h BezCoord.h BezCurve.h BezCurvePath.h ReadCurves.h AllocAndRead.h MorphDbg.h mathconst.h MathAlgo.h MathImpl.h number_type.h Hex.h HexGrid.h HdfData.h Process.h RD_Base.h DirichVtx.h DirichDom.h ShapeAnalysis.h NM_Simplex.h Anneal.h Config.h vec.h vvec.h TransformMatrix.h colour.h ColourMap.h ColourMap_Lists.h Scale.h Random.h RecurrentNetworkTools.h RecurrentNetwork.h Winder.h expression_sfinae.h base64.h + FILES Quaternion.h tools.h BezCoord.h BezCurve.h BezCurvePath.h ReadCurves.h AllocAndRead.h MorphDbg.h mathconst.h MathAlgo.h MathImpl.h number_type.h Hex.h HexGrid.h HdfData.h Process.h RD_Base.h DirichVtx.h DirichDom.h ShapeAnalysis.h NM_Simplex.h Anneal.h Config.h vec.h vvec.h TransformMatrix.h colour.h ColourMap.h ColourMap_Lists.h Scale.h Random.h RecurrentNetworkTools.h RecurrentNetwork.h Winder.h trait_tests.h base64.h Mnist.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/morph ) # There are also headers in sub directories diff --git a/morph/GraphVisual.h b/morph/GraphVisual.h index 6c1abe7c..fa9a8229 100644 --- a/morph/GraphVisual.h +++ b/morph/GraphVisual.h @@ -367,11 +367,10 @@ namespace morph { } //! Update the data for the graph, recomputing the vertices when done. - template < template typename Ctnr1, - template typename Ctnr2, - typename Alctr=std::allocator > - void update (const Ctnr1& _abscissae, - const Ctnr2& _data, const size_t data_idx) + template + std::enable_if_t::value + && morph::container_with_legacy_input_iterator::value, void> + update (const Ctnr1& _abscissae, const Ctnr2& _data, const size_t data_idx) { size_t dsize = _data.size(); @@ -390,12 +389,13 @@ namespace morph { // May need a re-autoscaling option somewhere in here. // Transfor the data into temporary containers sd and ad - Ctnr2 sd (dsize, Flt{0}); - this->ord1_scale.transform (_data, sd); - - Ctnr1 ad (dsize, Flt{0}); + std::vector ad (dsize, Flt{0}); this->abscissa_scale.transform (_abscissae, ad); + + std::vector sd (dsize, Flt{0}); + this->ord1_scale.transform (_data, sd); + // Now sd and ad can be used to construct dataCoords x/y. They are used to // set the position of each datum into dataCoords for (size_t i = 0; i < dsize; ++i) { @@ -469,18 +469,18 @@ namespace morph { //! Set a dataset into the graph using default styles, incrementing colour and //! marker shape as more datasets are included in the graph. - template < template typename Ctnr1, - template typename Ctnr2, - typename Alctr=std::allocator > - void setdata (const Ctnr1& _abscissae, const Ctnr2& _data, - const std::string name = "", const morph::axisside axisside = morph::axisside::left) + template + std::enable_if_t::value + && morph::container_with_legacy_input_iterator::value, void> + setdata (const Ctnr1& _abscissae, const Ctnr2& _data, + const std::string name = "", const morph::axisside axisside = morph::axisside::left) { DatasetStyle ds(this->policy); ds.axisside = axisside; if (!name.empty()) { ds.datalabel = name; } size_t data_index = this->graphDataCoords.size(); this->setstyle (ds, DatasetStyle::datacolour(data_index), DatasetStyle::datamarkerstyle (data_index)); - this->setdata (_abscissae, _data, ds); + this->setdata (_abscissae, _data, ds); } //! setdata overload that accepts vvec of coords (as morph::vec) @@ -499,11 +499,11 @@ namespace morph { //! Set a dataset into the graph. Provide abscissa and ordinate and a dataset //! style. The locations of the markers for each dataset are computed and stored - //! in this->graohDataCoords, one vector for each dataset. - template < template typename Ctnr1, - template typename Ctnr2, - typename Alctr=std::allocator > - void setdata (const Ctnr1& _abscissae, const Ctnr2& _data, const DatasetStyle& ds) + //! in this->graphDataCoords, one vector for each dataset. + template + std::enable_if_t::value + && morph::container_with_legacy_input_iterator::value, void> + setdata (const Ctnr1& _abscissae, const Ctnr2& _data, const DatasetStyle& ds) { if (_abscissae.size() != _data.size()) { std::stringstream ee; @@ -544,8 +544,8 @@ namespace morph { if (dsize > 0) { // Transform the data into temporary containers sd and ad - Ctnr1 ad (dsize, Flt{0}); - Ctnr2 sd (dsize, Flt{0}); + std::vector ad (dsize, Flt{0}); + std::vector sd (dsize, Flt{0}); if (ds.axisside == morph::axisside::left) { this->ord1_scale.transform (_data, sd); } else { @@ -613,10 +613,10 @@ namespace morph { protected: //! Compute the scaling of ord1_scale and abscissa_scale according to the scalingpolicies - template < template typename Ctnr1, - template typename Ctnr2, - typename Alctr=std::allocator > - void compute_scaling (const Ctnr1& _abscissae, const Ctnr2& _data, const morph::axisside axisside) + template + std::enable_if_t::value + && morph::container_with_legacy_input_iterator::value, void> + compute_scaling (const Ctnr1& _abscissae, const Ctnr2& _data, const morph::axisside axisside) { morph::range data_maxmin = morph::MathAlgo::maxmin (_data); morph::range absc_maxmin = morph::MathAlgo::maxmin (_abscissae); diff --git a/morph/MathAlgo.h b/morph/MathAlgo.h index c7e0c691..1ac770ec 100644 --- a/morph/MathAlgo.h +++ b/morph/MathAlgo.h @@ -39,18 +39,16 @@ namespace morph { /*******************************************************************/ /*! - * Functions whose implementations are in MathImpl, and which differ depending - * on whether T is a scalar type or a vector-like object (such as std::vector + * Functions whose implementations are in MathImpl, and which differ depending on whether + * the container's value_type is a scalar type or a vector-like object (such as std::vector * or std::list). * * Don't confuse this with C++11's std::minmax, which does something similar, * but won't do a max/min length of vector search like this does. */ - template < template typename Container, - typename T, - typename Allocator=std::allocator > - static morph::range maxmin (const Container& vec) { - return MathImpl::value>::maxmin (vec); + template ::value, int> = 0> + static morph::range maxmin (const Container& vec) { + return MathImpl::value>::maxmin (vec); } /*! diff --git a/morph/MathImpl.h b/morph/MathImpl.h index ef1ea10e..cfed2e61 100644 --- a/morph/MathImpl.h +++ b/morph/MathImpl.h @@ -25,7 +25,7 @@ #include #include #include -#include +#include namespace morph { @@ -47,11 +47,10 @@ namespace morph { struct MathImpl { //! Resizable and Fixed size vector maxmin implementations are common - template < template typename Container, - typename T, - typename Allocator=std::allocator > - static morph::range maxmin (const Container& values) + template ::value, int> = 0> + static morph::range maxmin (const Container& values) { + using T = typename Container::value_type; // Example to get the type of the container T. // See https://stackoverflow.com/questions/44521991/type-trait-to-get-element-type-of-stdarray-or-c-style-array using T_el = std::remove_reference_t()))>; @@ -233,11 +232,10 @@ namespace morph { struct MathImpl<1> { //! Scalar maxmin implementation - template < template typename Container, - typename T, - typename Allocator=std::allocator > - static morph::range maxmin (const Container& values) + template ::value, int> = 0> + static morph::range maxmin (const Container& values) { + using T = typename Container::value_type; morph::range r (std::numeric_limits::max(), std::numeric_limits::lowest()); for (auto v : values) { r.max = v > r.max ? v : r.max; diff --git a/morph/Scale.h b/morph/Scale.h index 27964756..7c54182c 100644 --- a/morph/Scale.h +++ b/morph/Scale.h @@ -119,13 +119,10 @@ namespace morph { * * \tparam OAllocator Memory allocator for OContainer. */ - template < template typename Container, - typename TT=T, - typename Allocator=std::allocator, - template typename OContainer=Container, - typename ST=S, - typename OAllocator=std::allocator > - void transform (const Container& data, OContainer& output) + template + std::enable_if_t::value + && morph::container_with_legacy_input_iterator::value, void> + transform (const Container& data, OContainer& output) { size_t dsize = data.size(); if (output.size() != dsize) { @@ -136,21 +133,18 @@ namespace morph { } else if (this->do_autoscale == false && !this->ready()) { throw std::runtime_error ("ScaleImplBase::transform(): Params are not set and do_autoscale is set false. Can't transform."); } - typename Container::const_iterator di = data.begin(); - typename Container::iterator oi = output.begin(); + typename Container::const_iterator di = data.begin(); + typename OContainer::iterator oi = output.begin(); while (di != data.end()) { *oi++ = this->transform_one (*di++); } } /*! * \brief Inverse transform a container of scalars or vectors. */ - template < template typename OContainer, - typename ST=S, - typename OAllocator=std::allocator, - template typename Container=OContainer, - typename TT=T, - typename Allocator=std::allocator > - void inverse (const Container& data, OContainer& output) + template + std::enable_if_t::value + && morph::container_with_legacy_input_iterator::value, void> + inverse (const Container& data, OContainer& output) { size_t dsize = data.size(); if (output.size() != dsize) { @@ -159,8 +153,8 @@ namespace morph { if (!this->ready()) { throw std::runtime_error ("ScaleImplBase::inverse(): Can't inverse transform; set params of this Scale, first"); } - typename Container::const_iterator di = data.begin(); - typename Container::iterator oi = output.begin(); + typename Container::const_iterator di = data.begin(); + typename OContainer::iterator oi = output.begin(); while (di != data.end()) { *oi++ = this->inverse_one (*di++); } } @@ -195,12 +189,11 @@ namespace morph { * \param data The data from which to determine the scaling parameters. In practice, this * will be something like \c std::vector or \c std::list> */ - template < template typename Container, - typename TT=T, - typename Allocator=std::allocator > - void autoscale_from (const Container& data) + template + std::enable_if_t::value, void> + autoscale_from (const Container& data) { - morph::range mm = MathAlgo::maxmin (data); + morph::range mm = MathAlgo::maxmin (data); this->compute_autoscale (mm.min, mm.max); } diff --git a/morph/Winder.h b/morph/Winder.h index 46c12f45..1d6be06b 100644 --- a/morph/Winder.h +++ b/morph/Winder.h @@ -14,7 +14,7 @@ #include #include #include -#include +#include namespace morph { diff --git a/morph/expression_sfinae.h b/morph/trait_tests.h similarity index 62% rename from morph/expression_sfinae.h rename to morph/trait_tests.h index 79632bc9..10cfd335 100644 --- a/morph/expression_sfinae.h +++ b/morph/trait_tests.h @@ -1,7 +1,7 @@ /*! * \file * - * Expression SFINAE template incantations. + * Type traits template incantations. * * This file contains numerous classes which can be used to test for features in class * types. It's useful for if constexpr () tests, and was initially @@ -16,8 +16,8 @@ namespace morph { - //! Expression SFINAE approach to testing for possibility of a-b. Could also make a - //! class which used std::is_arithmetic here. + //! Traits approach to testing for possibility of a-b. Could also make a class which used + //! std::is_arithmetic here. template class has_subtraction { @@ -27,7 +27,7 @@ namespace morph { static constexpr bool value = std::is_same(T{}, T{})),std::true_type>::value; }; - //! Expression SFINAE approach to testing for possibility of a+b. + //! Traits approach to testing for possibility of a+b. template class has_addition { @@ -37,7 +37,7 @@ namespace morph { static constexpr bool value = std::is_same(T{}, T{})),std::true_type>::value; }; - //! An expression SFINAE approach to testing for x() and y() methods + //! Traits approach to testing for x() and y() methods template class has_xy_methods { @@ -49,8 +49,8 @@ namespace morph { static constexpr bool value = std::is_same(0)), std::true_type>::value; }; - //! Expression SFINAE approach to testing for resize(size_t) method. Can be used to - //! distinguish std::array from std::vector. + //! Traits approach to testing for resize(size_t) method. Can be used to distinguish std::array + //! from std::vector. template class has_resize_method { @@ -61,9 +61,8 @@ namespace morph { static constexpr bool value = std::is_same< decltype(test(2)), std::true_type >::value; }; - //! Expression SFINAE approach to testing for x and y member attributes. I use this - //! to detect a class like cv::Point which has its coordinates set/accessed with .x - //! and .y + //! Traits approach to testing for x and y member attributes. I use this to detect a class like + //! cv::Point which has its coordinates set/accessed with .x and .y template class has_xy_members { @@ -74,7 +73,7 @@ namespace morph { static constexpr bool value = std::is_same(0)),std::true_type>::value; }; - // Expression SFINAE approach to testing for first and second member attributes + // Traits approach to testing for first and second member attributes template class has_firstsecond_members { @@ -85,7 +84,7 @@ namespace morph { static constexpr bool value = std::is_same(0)), std::true_type>::value; }; - // Expression SFINAE approach to testing for ability to access like an array (i.e. std::array, morph::vec) + // Traits approach to testing for ability to access like an array (i.e. std::array, morph::vec) template class array_access_possible { @@ -95,7 +94,7 @@ namespace morph { static constexpr bool value = std::is_same(0)),std::true_type>::value; }; - // Test for the has a const_iterator trait + // A Test for whether T has a const_iterator template class has_const_iterator { @@ -105,4 +104,23 @@ namespace morph { enum { value = sizeof(test(0)) == sizeof(char) }; }; + // Does T have a const_iterator which satisfies the requirements of LegacyInputIterator? + // Note this is NOT yet complete - I don't test std::iterator_traits. + template + class container_with_legacy_input_iterator + { + // Test C's const_iterator for traits copy constructible, copy assignable, destructible, swappable and equality comparable + template static auto test(int) -> decltype(std::is_copy_constructible::value == true + && std::is_copy_assignable::value == true + && std::is_destructible::value == true + && std::is_swappable::value == true + && std::declval == std::declval + , std::true_type()); + + template static int test(...); + + public: + static constexpr bool value = std::is_same(0)), std::true_type>::value; + }; + } // morph:: diff --git a/morph/vvec.h b/morph/vvec.h index a887d770..28fef54c 100644 --- a/morph/vvec.h +++ b/morph/vvec.h @@ -21,7 +21,7 @@ #include #include #include -#include +#include namespace morph { @@ -77,40 +77,22 @@ namespace morph { //! \return the fourth component of the vector S w() const { return (*this)[3]; } - //! Set data members from a templated container (by copying). Containers like - //! std::vector, std::deque and so on should work, but std::array, std::map won't. - // FIXME: Implement traits solution? https://stackoverflow.com/questions/7728478/c-template-class-function-with-arbitrary-container-type-how-to-define-it - template < template typename Ctnr, - typename _S=S, - typename Alctr=std::allocator<_S> > - void set_from (const Ctnr<_S, Alctr>& contained) - { - this->resize (contained.size()); - std::copy (contained.begin(), contained.end(), this->begin()); - } + //! This uses a traits solution inspired by + //! https://stackoverflow.com/questions/7728478/c-template-class-function-with-arbitrary-container-type-how-to-define-it -#if 1 - //! Set data members from an std::array, matching the size of the array first. - template - void set_from (const std::array<_S, N>& ar) - { - this->resize(N); - std::copy (ar.begin(), ar.end(), this->begin()); - } -#else - //! Traits set_from that could work with std::array? + //! Traits set_from that can work with sequential containers like std::array, deque, vector, + //! morph::vvec, morph::vec etc. template - std::enable_if_t::value, void> // Need more tests - satisfy LegacyInputIterator + std::enable_if_t::value, void> set_from (const Container& c) { this->resize (c.size()); std::copy (c.begin(), c.end(), this->begin()); } -#endif - //! Set all elements from the value type v. template - void set_from (const _S& v) { std::fill (this->begin(), this->end(), v); } + std::enable_if_t::value, void> + set_from (const _S& v) { std::fill (this->begin(), this->end(), v); } /*! * Set the data members of this vvec from the passed in, larger array, \a ar, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 280f2e4d..a62997e9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -291,6 +291,12 @@ add_test(testvvec_elementcompare testvvec_elementcompare) add_executable(testvvec_rescale testvvec_rescale.cpp) add_test(testvvec_rescale testvvec_rescale) +add_executable(testvvec_set_from testvvec_set_from.cpp) +add_test(testvvec_set_from testvvec_set_from) + +add_executable(test_trait_tests test_trait_tests.cpp) +add_test(test_trait_tests test_trait_tests) + add_executable(testarange testarange.cpp) add_test(testarange testarange) diff --git a/tests/test_trait_tests.cpp b/tests/test_trait_tests.cpp new file mode 100644 index 00000000..e21f10e0 --- /dev/null +++ b/tests/test_trait_tests.cpp @@ -0,0 +1,42 @@ +#include +#include + +template +std::enable_if_t < morph::container_with_legacy_input_iterator<_S>::value, bool > +set_from (const _S& v) +{ + std::cout << "Type _S=" << typeid(_S).name() << " size " << sizeof (v) << " CAN be a legacy input iterator" << std::endl; + return true; +} + +template +std::enable_if_t < !morph::container_with_legacy_input_iterator<_S>::value, bool > +set_from (const _S& v) +{ + std::cout << "Type _S=" << typeid(_S).name() << " size " << sizeof (v) << " can't be a legacy input iterator" << std::endl; + return false; +} + +#include +#include +#include +int main() +{ + float f = 0.0f; + bool float_can = set_from (f); + std::array c; + bool array_can = set_from (c); + std::vector c2; + bool vector_can = set_from (c2); + // I want false returned for std::map as this can't be set_from. So it's not JUST that map has + // to have a LegacyInputIterator, because you can't std::copy(map::iterator, map::iterator, + // vector::iterator). So leaving this FAILING for now. + std::map c3; + bool map_can = set_from (c3); + + if (float_can || !array_can || !vector_can || map_can) { + return -1; + } + + return 0; +} diff --git a/tests/testvvec_set_from.cpp b/tests/testvvec_set_from.cpp new file mode 100644 index 00000000..992b56e0 --- /dev/null +++ b/tests/testvvec_set_from.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include + +int main() +{ + int rtn = 0; + + std::vector svf = { 1, 2, 3, 4 }; + morph::vvec mvf; + mvf.set_from (svf); + std::cout << "mvf set from std::vector: " << mvf << std::endl; + if (mvf[0] != 1.0f || mvf[2] != 3.0f) { + --rtn; + } + + std::array af = { 2, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + mvf.set_from (af); + std::cout << "mvf set from std::array: " << mvf << std::endl; + if (mvf[0] != 2.0f || mvf[9] != 9.0f) { + --rtn; + } + + // set_from a scalar + mvf.set_from (1.0f); + std::cout << "mvf of size 10 set from float: " << mvf << std::endl; + if (mvf[0] != 1.0f || mvf[2] != 1.0f) { + --rtn; + } + + // Create a set and fill + std::set sf; + for (int i = 0; i < 12; ++i) { + sf.insert (static_cast(i)); + } + + mvf.set_from (sf); + std::cout << "mvf of size 10 set from std::set: " << mvf << std::endl; + if (mvf[0] != 0.0f || mvf[10] != 10.0f) { + --rtn; + } + + // set from a container of different type + std::array ai = { 2, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + mvf.set_from (ai); + std::cout << "mvf set from std::array: " << mvf << std::endl; + if (mvf[0] != 2.0f || mvf[9] != 9.0f) { + --rtn; + } + + std::cout << "Test is returning: " << rtn << std::endl; + return rtn; +} From ac12adcd62c166cdd818075a21901a8e8252d2d9 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 15 Dec 2023 15:56:30 +0000 Subject: [PATCH 3/4] Spent all day not really getting anywhere --- morph/GraphVisual.h | 16 +++++++-------- morph/MathAlgo.h | 2 +- morph/MathImpl.h | 4 ++-- morph/Scale.h | 10 +++++----- morph/trait_tests.h | 14 +++++++------ morph/vvec.h | 6 +++--- tests/test_trait_tests.cpp | 40 +++++++++++++++++++++++++++++-------- tests/testvvec_set_from.cpp | 8 ++++++++ 8 files changed, 67 insertions(+), 33 deletions(-) diff --git a/morph/GraphVisual.h b/morph/GraphVisual.h index fa9a8229..2d278d60 100644 --- a/morph/GraphVisual.h +++ b/morph/GraphVisual.h @@ -368,8 +368,8 @@ namespace morph { //! Update the data for the graph, recomputing the vertices when done. template - std::enable_if_t::value - && morph::container_with_legacy_input_iterator::value, void> + std::enable_if_t::value + && morph::is_copyable_container::value, void> update (const Ctnr1& _abscissae, const Ctnr2& _data, const size_t data_idx) { size_t dsize = _data.size(); @@ -470,8 +470,8 @@ namespace morph { //! Set a dataset into the graph using default styles, incrementing colour and //! marker shape as more datasets are included in the graph. template - std::enable_if_t::value - && morph::container_with_legacy_input_iterator::value, void> + std::enable_if_t::value + && morph::is_copyable_container::value, void> setdata (const Ctnr1& _abscissae, const Ctnr2& _data, const std::string name = "", const morph::axisside axisside = morph::axisside::left) { @@ -501,8 +501,8 @@ namespace morph { //! style. The locations of the markers for each dataset are computed and stored //! in this->graphDataCoords, one vector for each dataset. template - std::enable_if_t::value - && morph::container_with_legacy_input_iterator::value, void> + std::enable_if_t::value + && morph::is_copyable_container::value, void> setdata (const Ctnr1& _abscissae, const Ctnr2& _data, const DatasetStyle& ds) { if (_abscissae.size() != _data.size()) { @@ -614,8 +614,8 @@ namespace morph { protected: //! Compute the scaling of ord1_scale and abscissa_scale according to the scalingpolicies template - std::enable_if_t::value - && morph::container_with_legacy_input_iterator::value, void> + std::enable_if_t::value + && morph::is_copyable_container::value, void> compute_scaling (const Ctnr1& _abscissae, const Ctnr2& _data, const morph::axisside axisside) { morph::range data_maxmin = morph::MathAlgo::maxmin (_data); diff --git a/morph/MathAlgo.h b/morph/MathAlgo.h index 1ac770ec..865425bc 100644 --- a/morph/MathAlgo.h +++ b/morph/MathAlgo.h @@ -46,7 +46,7 @@ namespace morph { * Don't confuse this with C++11's std::minmax, which does something similar, * but won't do a max/min length of vector search like this does. */ - template ::value, int> = 0> + template ::value, int> = 0> static morph::range maxmin (const Container& vec) { return MathImpl::value>::maxmin (vec); } diff --git a/morph/MathImpl.h b/morph/MathImpl.h index cfed2e61..d2c563e3 100644 --- a/morph/MathImpl.h +++ b/morph/MathImpl.h @@ -47,7 +47,7 @@ namespace morph { struct MathImpl { //! Resizable and Fixed size vector maxmin implementations are common - template ::value, int> = 0> + template ::value, int> = 0> static morph::range maxmin (const Container& values) { using T = typename Container::value_type; @@ -232,7 +232,7 @@ namespace morph { struct MathImpl<1> { //! Scalar maxmin implementation - template ::value, int> = 0> + template ::value, int> = 0> static morph::range maxmin (const Container& values) { using T = typename Container::value_type; diff --git a/morph/Scale.h b/morph/Scale.h index 7c54182c..5d538321 100644 --- a/morph/Scale.h +++ b/morph/Scale.h @@ -120,8 +120,8 @@ namespace morph { * \tparam OAllocator Memory allocator for OContainer. */ template - std::enable_if_t::value - && morph::container_with_legacy_input_iterator::value, void> + std::enable_if_t::value + && morph::is_copyable_container::value, void> transform (const Container& data, OContainer& output) { size_t dsize = data.size(); @@ -142,8 +142,8 @@ namespace morph { * \brief Inverse transform a container of scalars or vectors. */ template - std::enable_if_t::value - && morph::container_with_legacy_input_iterator::value, void> + std::enable_if_t::value + && morph::is_copyable_container::value, void> inverse (const Container& data, OContainer& output) { size_t dsize = data.size(); @@ -190,7 +190,7 @@ namespace morph { * will be something like \c std::vector or \c std::list> */ template - std::enable_if_t::value, void> + std::enable_if_t::value, void> autoscale_from (const Container& data) { morph::range mm = MathAlgo::maxmin (data); diff --git a/morph/trait_tests.h b/morph/trait_tests.h index 10cfd335..74906bb9 100644 --- a/morph/trait_tests.h +++ b/morph/trait_tests.h @@ -94,20 +94,22 @@ namespace morph { static constexpr bool value = std::is_same(0)),std::true_type>::value; }; - // A Test for whether T has a const_iterator +#if 0 // haven't figured out how to make this work. template - class has_const_iterator + class has_find_method { - template static char test(typename C::const_iterator*); - template static int test(...); + template static auto test(int) -> decltype(std::declval().find(std::declval().end()), std::true_type()); + template static std::false_type test(...); public: - enum { value = sizeof(test(0)) == sizeof(char) }; + static constexpr bool value = std::is_same< decltype(test(0)), std::true_type >::value; }; +#endif // Does T have a const_iterator which satisfies the requirements of LegacyInputIterator? // Note this is NOT yet complete - I don't test std::iterator_traits. + // The tests here more or less tell me if I have a copyable container template - class container_with_legacy_input_iterator + class is_copyable_container { // Test C's const_iterator for traits copy constructible, copy assignable, destructible, swappable and equality comparable template static auto test(int) -> decltype(std::is_copy_constructible::value == true diff --git a/morph/vvec.h b/morph/vvec.h index 28fef54c..b07bbfba 100644 --- a/morph/vvec.h +++ b/morph/vvec.h @@ -81,9 +81,9 @@ namespace morph { //! https://stackoverflow.com/questions/7728478/c-template-class-function-with-arbitrary-container-type-how-to-define-it //! Traits set_from that can work with sequential containers like std::array, deque, vector, - //! morph::vvec, morph::vec etc. + //! morph::vvec, morph::vec etc and even with std::set (though not with std::map) template - std::enable_if_t::value, void> + std::enable_if_t::value, void> set_from (const Container& c) { this->resize (c.size()); @@ -91,7 +91,7 @@ namespace morph { } //! Set all elements from the value type v. template - std::enable_if_t::value, void> + std::enable_if_t::value, void> set_from (const _S& v) { std::fill (this->begin(), this->end(), v); } /*! diff --git a/tests/test_trait_tests.cpp b/tests/test_trait_tests.cpp index e21f10e0..7cd925b1 100644 --- a/tests/test_trait_tests.cpp +++ b/tests/test_trait_tests.cpp @@ -1,25 +1,44 @@ #include #include +#include +#if 0 +template +bool set_from (const std::set& st) +{ + std::cout << "Type set=set<" << typeid(F).name() << "> size " << sizeof (st) << " is a set container" << std::endl; + return true; +} +#endif template -std::enable_if_t < morph::container_with_legacy_input_iterator<_S>::value, bool > +std::enable_if_t < morph::is_copyable_container<_S>::value, bool > set_from (const _S& v) { - std::cout << "Type _S=" << typeid(_S).name() << " size " << sizeof (v) << " CAN be a legacy input iterator" << std::endl; - return true; +#if 0 + if constexpr (morph::has_find_method<_S>::value == true) { + std::cout << "Type _S=" << typeid(_S).name() << " size " << sizeof (v) << " seems to be a map container: " << morph::has_find_method<_S>::value << std::endl; + return false; + } else { +#endif + std::cout << "Type _S=" << typeid(_S).name() << " size " << sizeof (v) << " is a simple, copyable container" << std::endl; + return true; +#if 0 + } +#endif } template -std::enable_if_t < !morph::container_with_legacy_input_iterator<_S>::value, bool > +std::enable_if_t < !morph::is_copyable_container<_S>::value, bool > set_from (const _S& v) { - std::cout << "Type _S=" << typeid(_S).name() << " size " << sizeof (v) << " can't be a legacy input iterator" << std::endl; + std::cout << "Type _S=" << typeid(_S).name() << " size " << sizeof (v) << " isn't a container" << std::endl; return false; } #include #include #include + int main() { float f = 0.0f; @@ -31,12 +50,17 @@ int main() // I want false returned for std::map as this can't be set_from. So it's not JUST that map has // to have a LegacyInputIterator, because you can't std::copy(map::iterator, map::iterator, // vector::iterator). So leaving this FAILING for now. - std::map c3; - bool map_can = set_from (c3); + //std::map c3; + //bool map_can = set_from (c3); + + std::set c4; + bool set_can = set_from (c4); - if (float_can || !array_can || !vector_can || map_can) { + if (float_can || !array_can || !vector_can || !set_can) { + std::cout << "Test failed\n"; return -1; } + std::cout << "Test passed\n"; return 0; } diff --git a/tests/testvvec_set_from.cpp b/tests/testvvec_set_from.cpp index 992b56e0..ee7f4d83 100644 --- a/tests/testvvec_set_from.cpp +++ b/tests/testvvec_set_from.cpp @@ -51,6 +51,14 @@ int main() --rtn; } + // set from a set + std::set si = { 2, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + mvf.set_from (si); + std::cout << "mvf set from std::set: " << mvf << std::endl; + if (mvf[0] != 1.0f || mvf[8] != 9.0f) { + --rtn; + } + std::cout << "Test is returning: " << rtn << std::endl; return rtn; } From 95ea1553e5ea93cb984e041a787a99677df8a647 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 18 Dec 2023 10:46:32 +0000 Subject: [PATCH 4/4] more set from tests --- tests/testvvec_set_from.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/testvvec_set_from.cpp b/tests/testvvec_set_from.cpp index ee7f4d83..868460e8 100644 --- a/tests/testvvec_set_from.cpp +++ b/tests/testvvec_set_from.cpp @@ -59,6 +59,20 @@ int main() --rtn; } + morph::vec vecf = { 2, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + mvf.set_from (vecf); + std::cout << "mvf set from morph::vec: " << mvf << std::endl; + if (mvf[0] != 2.0f || mvf[9] != 9.0f) { + --rtn; + } + + morph::vvec vvecf = { 1, 2, 3, 4 }; + mvf.set_from (svf); + std::cout << "mvf set from morph::vvec: " << mvf << std::endl; + if (mvf[0] != 1.0f || mvf[2] != 3.0f) { + --rtn; + } + std::cout << "Test is returning: " << rtn << std::endl; return rtn; }