C++ nem a specializált template-et használja

C++ nem a specializált template-et használja
2017-12-07T13:56:13+01:00
2017-12-08T14:34:02+01:00
2022-10-15T21:30:22+02:00
x00
2 esetben is tapasztaltam hogy bár van specializált sablon, mégis az általánosat használja. A csatolt fájl 2 példát tartalmaz, hibát ad ha a megjegyzésben levő változatot használom.

Mivel a C++ saját abs függvénye a legkisebb ábrázolható negatív számot úgyhagyja negatívan, hát csináltam sajátot, hogy a neve ne ütközzön, xabs néven, mert sajnos az ::abs már az #include <type_traits>-tól is van, nem csak std::abs néven érhető el. A make_unsigned valósakra nem működik, így az eredménytípus megadásánál hiba. Csakhogy én specializáltam valósakra. Vagyis valós esetén is az általánosat akarja használni. A SFINAE a neve szerint csak a specializáltakra vonatkozik, úgy néz ki az általánosnak mindenre működnie kell, legalábbis a prototípusnak, a törzsnek nem. A másikat viszont nem értem:

Mivel a tömb a C++ mostohagyereke, nincs rá =, == se, és mindig cím szerint adja át, hát csináltam olyan =-t set néven ami tömbre is megy. Ennél ha specializálom, az általánosat használja ha a forrás const referencia, normál értékként átadáskor viszont a specializáltat. Ha mindkét változatban van, akkor is az általánosat használja, amit szintén nem értek. Nem engedi kiírni elé hogy template<>, pedig t1 = const int & esetén pont az kellene legyen. Mik itt a szabályok és hogy lehet megcsinálni hogy az általános és a specializált is működjön, ne kelljen más nevet adni a függvénynek?

A csatolt fájlban a tab 4 szóköz, Visual C++ 2017-tel fordítottam AMD64-re, fejléces UTF-8 formátumú, ha ezt nem tudja valamely fordító, az elejéről a 3 byte-os UTF-8 fejlécet törölni kell (menteni más kódolásba, ugyanis UTF-8-at támogató editorban se látszik a típusjelző fejléc). Sajnos 64 bites Internet Explorer 11-ben a forráskód beillesztésére való </>-t egyáltalán nem tudom használni, sokszor próbáltam, tabok helyett is szóközt ír.

#include <type_traits> // Compiler error (uses general for specialized types, make_unsigned is not usable to real types): // template<typename t> typename std::make_unsigned<t>::type xabs(const t x); // template<typename t, typename tt = std::make_unsigned<t>::type> tt xabs(const t x); // template<typename t> auto xabs(const t x) -> std::make_unsigned<t>::type; // template<typename t> auto xabs(const t x) -> decltype((x < 0) ? -x : x); template<typename t> auto xabs(const t x) { typedef typename std::make_unsigned<t>::type tt; return (x < 0) ? (tt) -x : (tt) x; } float xabs(const float x) { return (x < 0) ? -x : x; } double xabs(const double x) { return (x < 0) ? -x : x; } // Assignment to arrays so template<typename t0, typename t1> void set(t0 &x, t1 &&y) { x = (t1 &&) y; } template<typename t0, typename t1, size_t n> void set(t0 (&x)[n], t1 (&y)[n]) { for (size_t i = 0; i < n; i++) set(x, y); } template<typename t0, typename t1, size_t n> void set(t0 (&x)[n], const t1 (&y)[n]) { for (size_t i = 0; i < n; i++) set(x, y); } template<typename t0, typename t1, size_t n> void set(t0 (&x)[n], t1 (&&y)[n]) { for (size_t i = 0; i < n; i++) set(x, (t1 &&) (y)); } template<typename t0, typename t1, size_t n> void set(t0 (&x)[n], const t1 (&&y)[n]) { for (size_t i = 0; i < n; i++) set(x, (const t1 &&) (y)); } // User type: there is no operator= between int <-> empty class empty {}; // Uses general instead of these when source is a reference, there is no operator= // Compiler error: delete the 2 //, there are compiler error even all the 4 exist void set(empty &, const int) {} void set(int &, const empty) {} //void set(empty &, const int &) {} //void set(int &, const empty &) {} int wmain() { xabs(0.0); // there is ::abs, so other name required int i = 0; empty e; set(e, i); set(i, e); return 0; }
Köszönöm.
Mutasd a teljes hozzászólást!
Csatolt állomány
Én elsőnek a set -et vizsgálom

A set -nél nincs szó SFINAE -ről, mert nem template.
A gond, hogy kevered az érték és referencia szerinti function overload-ot, és nem jól.
Vagy ne overload-olj referencia szerint, vagy használj lvalue és rvalue referenciát.

void set(empty &, int &) {puts("Version 1");} void set(empty &, int&&) {puts("Version 2");} int main() { int i = 0; empty e; set(e, i); // Prints Version 1 set(e, 7); // Prints Version 2 }
Mutasd a teljes hozzászólást!

  • Most nincs időm bele menni, de már itt megakadtam:

    Mivel a C++ saját abs függvénye a legkisebb ábrázolható negatív számot úgyhagyja negatívan, hát csináltam sajátot

    Szerintem valamit benéztél, vagy bug-ot találtál a fordítódba, de inkább benéztél valamit :)

    Ideone.com

    #include <iostream> #include <limits> #include <cmath> using namespace std; int main() { cout<<(fabs( -numeric_limits<float>::max()) == -numeric_limits<float>::max()) <<(fabs( -numeric_limits<float>::min()) == -numeric_limits<float>::min()); return 0; }
    Mutasd a teljes hozzászólást!
  • A legnegatívabb integerre gondolt (0x80, 0x8000, etc) aminek az ellentettje nem fér el 8, 16, etc biten.
    Mutasd a teljes hozzászólást!
  • A gond hogy a

    float xabs(const float);
    Nem egy spcializációja a

    template<typename t> auto xabs(const t x);
    -nak. A float -os verzió csak egy overload-ja az xabs-nek. Template fügvény specializálni a kövekező képen kell.

    template<typename t> auto xabs(t x) -> t{ } template<> auto xabs(float x) -> float{ } template<> auto xabs(double x) -> double { } int main() { xabs(0.0); }
    Viszont a gond az, hogy a teljes specializóció szignaturájának meg kell egyezni a primary template szignaturájával.
    Mutasd a teljes hozzászólást!
  • #include <type_traits> namespace details { template<class T> struct make_unsigned { using type = std::make_unsigned_t<T>; }; template<> struct make_unsigned<float> { using type = float; }; template<> struct make_unsigned<double> { using type = double; }; template<class T> using make_unsigned_t = typename make_unsigned<T>::type; } template<class t, class R = details::make_unsigned_t<t>> auto xabs(t x) -> R { return ((x < 0) ? -x : x); } template<> auto xabs(float x) -> float { return ((x < 0.f) ? -x : x); } template<> auto xabs<double, double>(double x) -> double { return ((x < 0.0) ? -x : x); } int main() { xabs(-1); xabs(1.f); xabs(-2.0); }
    Mutasd a teljes hozzászólást!
  • Köszi, erre én is gondoltam már rég, csak megerősítést kértem hogy az-e az oka amit gondoltam. Végülis az auto megoldja, csak hát mivel programozok, tudjam már mi az oka, később még sokszor kellhet. Egyébként a te megoldásaid közül a korábbi nem fordul le, de gondolom ezt tudtad is: ha auto és -> is van, úgy veszi mintha eleve a -> utánit írtuk volna eredménytípusnak, és annál is a make_unsigned-et akarja használni.

    Természetesen a 32 és 64 bites int legkisebb ábrázolható értékét hagyja úgy negatívan ahogy van a C++ abs függvénye: - 2^31 és -2^63, ha 64 bites intben van - 2^31 az természetesen jó. Tudom hogy a specializáció úgy kezdődik hogy template<>, de kipróbáltam vele és nélküle is, pontosan ugyanazokat fogadja el, a többinél pedig pontosan azokat a hibákat adja, így elkönyveltem hogy felesleges kiírni, így nem írtam ki. Akit zavar az gondolja oda, írja be magának.

    Az összes set 2 paraméteres, és csak olyannal megy aminél x írható. A legelőrébblevő pedig minden ilyennel megy, így az az általános, a többi a specializáció-szerűsége.

    template<typename t0, typename t1> void set(t0 &x, t1 &&y)  { x = (t1 &&) y; }

    Például set(z, 0) esetén t1 = const int &. Az egyik problémám hogy miért nem engedi kitenni hogy template<>, miért veszi úgy hogy nem specializációja. Ha a forrást érték szerint adom át, megy, mindegy hogy nem specializáció, de azt csinálja amit kell. Ha viszont én const referenciaként szeretném átadni a forrást, tehát nem érték szerint, akkor már nem tudom megcsinálni. Miért? Hogy lehet megcsinálni?
    Mutasd a teljes hozzászólást!
  • Valamilyen oknál fogva már nem engedi szerkeszteni a hozzászólásomat, úgyhogy külön írom. Egyszerű int &&-ként se megy. Az általános jó módosítható y-ra is, úgy veszi mintha arra is specializálva lenne, így arra is specializálni kell:

    template<> void set<empty, const int &>(empty &, const int &) {}
    template<> void set<int, const empty &>(int &, const empty &) {}
    template<> void set<empty, int &>(empty &, int &) {}
    template<> void set<int, empty &>(int &, empty &) {}
    De set(e, 0); esetén még ezek se elegek, érték szerinti paraméterátadásra is kell specializálni. Azt hogy tudom megoldani hogy mondjuk konstansot is const referenciaként adjon át? A hátsó 2-nél a set után is kell a <>, nélküle nem veszi észre hogy specializáció: nem tudja levezetni.

    #include <iostream> void set(empty &, int &&) { std::cout << "&&\n"; }
    void set(int &, empty &&) { std::cout << "&&\n"; }
    void set(empty &, const int &) { std::cout << "const &\n"; }
    void set(int &, const empty &) { std::cout << "const &\n"; }
    void set(empty &, int &) { std::cout << "&\n"; }
    void set(int &, empty &) { std::cout << "&\n"; }
    Ezzel már megy, a const &, vagyis a középső 2 nem is kell, úgyse használja: még set(e, 0); esetén is &&-t használja, ami viszont azt jelenti hogy lemásolja, ami int-nél is plusz idő, de nagyméretűnél méginkább. Hogy lehet megoldani hogy például egész konstans esetén ne hozzon létre külön változót, melyet minden egyes hívás előtt fel is kell tölteni a && miatt, hanem const &-osan kezelje? Megoldható hogy ne kelljen 2-4 féle változatban megcsinálni?

    Ja most látom hogy ez pont a te megoldásod. Szóval ha például ciklusban van, így minden egyes hívás előtt fel kell tölteni a változót konstanssal.
    Mutasd a teljes hozzászólást!
  • Amennyiben SFINAE-t használnál, akkor az enable_if lesz a barátod.

    template < class T, class = enable_if_t<is_floating_point<T>::value> > T xabs(T x) { return fabs(x); }
    Később concept-ekkel meg még szebben meglehet majd oldani:
    (g++-ban belehet kapcsolni a concept lite-ot -fconcepts opcióval, visual studio esetén nem tudom van-e hasonló lehetőség)

    template <class T> requires is_floating_point<T>::value T xabs(T x) { return fabs(x); }
    Mutasd a teljes hozzászólást!
  • Ezekkel az új dolgokkal annyi a gond, hogy alkalmazni nem nagyon lehet, mert ugyan implementálva van az egész, de a komplexebb dolgok bugosak. Használatának a vége az lenne, hogy a fele kód régi technikát a másik fele pedig az újat használja ami nem valami szép.

    Az alábbi példának is működnie kéne, de a c++17-es és a concept-es példára is hibát ír. (clangel fordul a c++17-es példa, illetve GCC-vel is megy ha a deduction guidenál template<typename... Args, typename...> van.

    #include <tuple> template<typename... T> class Wrapper { public: template<typename... Args> Wrapper(Args&&... args) : m_data{std::forward<Args>(args)...} {} private: std::tuple<T...> m_data; }; template<typename... Args> Wrapper(Args&&...) -> Wrapper<Args...>; void foo(std::tuple<auto...>) {} int main() { Wrapper w{1, 2}; // c++17 foo(std::tuple{1, 2}); // concept }
    Mutasd a teljes hozzászólást!
  • C++17-et még nem is használnék éles projektbe. Szerintem nem is állítja eddig fordító se, hogy tudja/támogatja 100%-osan. Még csak exeperimental.
    C++14 viszont tapasztalataim szerint már bőven használható éles projektben is.
    (Concept nagyon előre mutató, hiszen C++17-be sem lesz benne, viszont ettől függetlenül nagyon jó dolognak tartom, már éles projektben is használtam).

    Konkrét példádnál maradva egy make_wrapper, make_tuple, foo-ban 'auto...' helyett kiírt variadic template-el simán működik. Minimális overhead, C++17 majd még jobb lesz, ha már több fordító teljesen támogatni fogja és stabil implementációja lesz :)
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd