[C++] functor calls.. std::transform wrappen

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
Beste Tweakers,

Ik gebruik best wel eens std::vectors waarbij ik de inhoud van een type naar een ander moet converteren.

Ik dacht dit eens generiek op te lossen door een std::transform oplossing te wrappen:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// standard caster functions
template <typename T>
struct static_cast_func
{
    template <typename T1>
    T operator()(const T1& x) const
    {
        return static_cast<T>(x);
    }
};

// converts elements of type T1 in input vector to type T2 and put in output vector
// conversion by default happens with a functor wrapping static cast, but user can
// provide his own functor if needed
template <class T1, class T2, class F1>
inline void ConvertSeries (const std::vector<T1> is, std::vector<T2>& os, F1 func)
{
    os.clear();
    std::transform(is.begin(), is.end(), std::back_inserter(os), func());
}

// geen default template arguments bij functies, dus dan maar zo
// (weet niet of ik wel een default argument voor func hierboven zou
// kunnen opgeven, compiler zou daar het type van F1 uit moeten
// kunnen halen, maar met de andere errors waar ik tegenaan loop
// kan ik dit niet testen)
template <class T1, class T2>
inline void ConvertSeries (const std::vector<T1> is, std::vector<T2>& os)
{
    ConvertSeries(is, os, static_cast_func<T2>());
}

wat ik dan bijvoorbeeld zo dacht aan te roepen:
C++:
1
2
3
4
5
ConvertSeries(bgColor_, bgColor, static_cast_func<float>());
// of, aangezien dit de standaard functor is:
ConvertSeries(bgColor_, bgColor);
// vergelijk met:
std::transform(bgColor_.begin(), bgColor_.end(), std::back_inserter(bgColor), static_cast_func<float>());


Dit compiled niet met de melding: "error C2780: 'float static_cast_func<T>::operator ()(const T1 &) const' : expects 1 arguments - 0 provided"

Met een beetje testen denk ik dat dit komt omdat de functie niet juist geinlined kan worden (als ik de body van de eerste ConvertSeries simpelweg return func(1.) (en het return type natuurlijk T2 maak), dan compileerd het prima). Vervelend.

Maargoed, hoe zouden jullie mijn probleem oplossen? Het gaat mij er vooral om dat ik makkelijk kan converteren, gebruik makend van een default functor of een user-provided.

Bedankt voor het meedenken!

Acties:
  • 0 Henk 'm!

  • xos
  • Registratie: Januari 2002
  • Laatst online: 10-06 12:27

xos

Op regel 19 probeer je de () operator aan te roepen van F1 met 0 argumenten terwijl je een instantie wilt meegeven. Verander dat eens naar:

C++:
1
2
3
4
5
6
template <class T1, class T2, class F1> 
inline void ConvertSeries (const std::vector<T1> is, std::vector<T2>& os, F1) 
{ 
    os.clear(); 
    std::transform(is.begin(), is.end(), std::back_inserter(os), F1()); 
} 

Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
Bedankt, dat compiled inderdaad, bedankt!

Maar nu werdt er een nieuwe instantie gemaakt in ConvertSeries, toch?
wat test code:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <vector>

// standard caster function
template <typename T>
struct static_cast_func
{
    template <typename T1>
    T operator()(const T1& x) const
    {
        return static_cast<T>(x);
    }
};

// converts elements of type T1 in input vector to type T2 and put in output vector
// conversion by default happens with a functor wrapping static cast, but user can
// provide his own functor if needed
template <class T1, class T2, class F1>
inline void ConvertSeries (const std::vector<T1> is, std::vector<T2>& os, F1 func)
{
    std::cout << &F1() << std::endl;
    os.clear();
    std::transform(is.begin(), is.end(), std::back_inserter(os), func);
}

template <class T1, class T2>
void ConvertSeries (const std::vector<T1> is, std::vector<T2>& os)
{
    static_cast_func<T2> st;
    std::cout << &st << std::endl;
    ConvertSeries(is, os, st);
}


int main()
{
    std::vector<float>  bgColorf;
    std::vector<double> bgColord;

    bgColord.push_back(.4);
    bgColord.push_back(.5);
    bgColord.push_back(.6);
    bgColord.push_back(.7);

    ConvertSeries(bgColord,bgColorf);

    return 0;
}


de twee cout's geven mij verschillende adressen terug. Nu is dit in mijn geval geen probleem, maar als ik een keer een functor met interne state nodig heb is dit wel een probleem. Ik heb het daarom zoals je in het code fragment ziet net iets anders opgelost met het stukje inspiratie dat je me gaf. Is dit een nette oplossing?
Bedankt!

Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

Dat kan, maar je zit dan wel met kopietjes van F1 te werken (tis immers een pass by value), dus als je dure objecten gaat rondgooien met interne state, wil je misschien (const) references gebruiken.

(Daarnaast, is het intended dat je "is" argument geen reference is?)

-niks-


Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
Hoi MLM,

Je hebt gelijk, d'r missen een paar ampersands, ik wil alleen references gebruiken hier om niets te kopieren.

Bedankt!

Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
Ok, fyi, heb er nu dit van gemaakt aangezien ik 99% van de tijd dit soort gedrag wil zien. Hoop dat iemand er wat aan heeft.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <vector>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>

// Like python, output is created by applying provided functor to each element
// in the input vector (just a convenience wrapper around std::transform)
template <class T1, class T2, class F1>
inline void MapVector(const std::vector<T1>& is, std::vector<T2>& os, F1& func)
{
    os.clear();
    std::transform(is.begin(), is.end(), std::back_inserter(os), func);
}

// standard caster function
template <typename T>
struct static_cast_func
{
    template <typename T1>
    typename boost::enable_if   // special case if converting from floating point type to integral type: round to nearest integer
    <
        boost::type_traits::ice_and
        <
            boost::is_integral<T>::value,
            boost::is_floating_point<T1>::value
        >
    , T>::type        // integral types
    operator()(const T1& x) const
    {
        return static_cast<T>(x+T1(.5));
    }

    template <typename T1>
    typename boost::disable_if   // all other cases
    <
        boost::type_traits::ice_and
        <
            boost::is_integral<T>::value,
            boost::is_floating_point<T1>::value
        >
    , T>::type
    operator()(const T1& x) const
    {
        return static_cast<T>(x);
    }
};

// converts elements of type T1 in input vector to type T2 and put in output vector
// conversion by default happens with a functor wrapping static cast, but user can
// provide his own functor if needed (use MapVector in that case)
template <class T1, class T2>
inline void ConvertSeries(const std::vector<T1>& is, std::vector<T2>& os)
{
    MapVector(is, os, static_cast_func<T2>());
}

Acties:
  • 0 Henk 'm!

  • xos
  • Registratie: Januari 2002
  • Laatst online: 10-06 12:27

xos

Persoonlijk zie ik weinig voordeel boven een direct aanroep naar transform. Transform werkt op input/output iterators en dus alle containers die deze types ondersteunen (eventueel via een wrapper als een back_inserter). Zo complex is dat toch niet om dat in functies weg te abstractheren?

Eigenlijk zou ik verwachten dat dit ook niet compileert omdat op regel 53 een temporary wordt gemaakt die aan een non const reference parameter wordt doorgegeven. Het staat de stl implementators trouwens vrij om te doen en laten met functors wat ze willen. Maw, in stl zal de functor mogelijk ook by value worden doorgegeven. Dus ik zou er persoonlijk niet zo zwaar aan tillen om de functor by value door te geven.

[ Voor 21% gewijzigd door xos op 03-07-2011 10:16 ]


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 09:23

.oisyn

Moderator Devschuur®

Demotivational Speaker

MLM schreef op zaterdag 02 juli 2011 @ 22:11:
Dat kan, maar je zit dan wel met kopietjes van F1 te werken (tis immers een pass by value)
Is gebruikelijk voor functors. Een non-const ref wil je ook niet want die bind weer niet aan temporaries, en dus krijg je de issue waar xos het over heeft. En een const ref heeft ook weer een const operator() nodig.

In C++-2011 gebruik je typisch een r-value reference: F1&& func.

[ Voor 6% gewijzigd door .oisyn op 03-07-2011 22:55 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
Bedankt xos and .oisyn,

Ik zie het probleem met de non-const reference, hoewel het hier wel compileerd, en ik zie dat std::transform van Microsoft hier idd ook gewoon per value gaat, dus de functor gaat weer per value. Verder vind ik het wrappen wel fijn omdat dit de form is die ik bijna altijd nodig heb.

Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 18-06 11:36
os.append(is.begin(), is.end()); is veel simpeler.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein

Pagina: 1