[Szablony] Uniwersalny kontener

0

Witam,
Potrzebuję stworzyć klasę, która będzie jakby uniwersalnym kontenerem na zmienne. Chciałbym aby klasa ta posiadała co najmniej dwie metody, jedna do dodawania zmiennej do kontenera, druga np. do przypisywania zmiennym wartości. Aby dokładniej zobrazować o co mi chodzi pozwólcie, że posłużę się przykładem:

class KontenerNaZmienne
{
public:
	template <class T>
	void dodaj(const std::string& nazwa, T& referencja)
	{
	}

	void przypisz(const std::string& NazwanaZmienna, const std::string& wartosc)
	{
	}
};

Coś mniej więcej w ten deseń, chciałbym aby nie trzeba było do realizacji tego używać void*
Potem, gdybym miał już taki kontener, chciałbym móc działać sobie na nim w ten sposób:

KontenerNaZmienne kont;

        int mojInt(0);
	double mojDouble(0.);
	std::string mojString("");

	kont.dodaj("integer", mojInt);
	kont.dodaj("dbl", mojDouble);
	kont.dodaj("str", mojString);

	kont.przypisz("integer", "15");
	kont.przypisz("dbl", "45.9");
	kont.przypisz("str", "wacek");

To tyle jeśli chodzi o życzenia :-) Teraz co wiem:
Wiem że należy to zrobić na szablonach
Wiem że można wykorzystać Boost MPL, niestety nie miałem jeszcze okazji się z nią zapoznać [wstyd]
I to chyba tyle, najgorsze jest to że nie mam nawet pomysłu jak się za to zabrać. W oczekiwaniu na odpowiedź kogoś kto miał z takimi cudami już do czynienia zagłębię się w lekturę dokumentacji Boost MPL :-P

0

Dzięki za odpowiedź, elegancko to wygląda, jednak gdy próbuję się zapoznać z tą biblioteką to nie mogę skompilować nawet przykładu [???]

#include <list>
#include <boost/any.hpp>

using boost::any_cast;
typedef std::list<boost::any> many;

void append_int(many & values, int value)
{
    boost::any to_append = value;
    values.push_back(to_append);
}

void append_string(many & values, const std::string & value)//ta funkcja bardzo się kompilatorowi nie podoba, jak ją usuwam to się kompiluje, błędy jakie wyrzuca są pod kodem
{
    values.push_back(value);
}

void append_char_ptr(many & values, const char * value)
{
    values.push_back(value);
}

void append_any(many & values, const boost::any & value)
{
    values.push_back(value);
}

void append_nothing(many & values)
{
    values.push_back(boost::any());
}

MinGW wyrzuca błędy:

36 instantiated from `boost::any::any(const ValueType&) [with ValueType = std::basic_string<char, std::char_traits<char>, std::allocator<char> >]' 
128 `boost::any::holder<ValueType>::held' has incomplete type
56 declaration of `struct std::basic_string<char, std::char_traits<char>, std::allocator<char> >'  
 In constructor `boost::any::holder<ValueType>::holder(const ValueType&) [with ValueType = std::basic_string<char, std::char_traits<char>, std::allocator<char> >]': 
36 instantiated from `boost::any::any(const ValueType&) [with ValueType = std::basic_string<char, std::char_traits<char>, std::allocator<char> >]' 
111 no matching function for call to `boost::any::placeholder::placeholder(const std::basic_string<char, std::char_traits<char>, std::allocator<char> >&)' 
 note candidates are: boost::any::placeholder::placeholder() 

Hardcore :-/ Czemu akurat przy stringu protestuje a przy innych nie?

Edit: lol nagle mi się skompilowało, chociaż nic nie zmieniałem, i teraz już się za każdym razem kompiluje :-D

0

Jeszcze nie mam boosta, to nie mam jak sprawdzić - spróbuj dołączyć string przed boost/any.hpp.

0

Już działa mi ten przykładzik, mniej więcej wiem od czego zacząć, w przypadku problemów wrzucę kod na forum.

0

Dobra, wydaje się to nawet nie takie trudne, jednak zaciąłem się w jednym miejscu. Oto kod:

struct property
{
    property(){};
    property(const std::string Name, boost::any Value)
    {
        name=Name;
        value=Value;
    };

    std::string name;
    boost::any value;
};

typedef std::list<property> container;

class KontenerNaZmienne
{
        container kontener;
public:
        template <class T>
        void dodaj(const std::string& nazwa, T& referencja)
        {
         property temp;
        temp.name=nazwa;
        temp.value=referencja;
        kontener.push_back(temp);
        }

        void przypisz(const std::string& NazwanaZmienna, const std::string& wartosc)
        {
         for(std::list<property>::iterator it=kontener.begin();it!=kontener.end(),it++)//tu protestuje
        {
            if(it->name==NazwanaZmienna)
            it->value=wartosc;
            break;
        }
        }
};

daje taki błąd:
could not convert ((void)(&it)->std::_List_iterator<_Tp>::operator!= [with _Tp = property](((const std::_List_iterator<property>&)((const std::_List_iterator<property>)(&((container)this)->std::list<_Tp, _Alloc>::end with _Tp = property, _Alloc = std::allocator<property>)))), (&it)->std::_List_iterator<_Tp>::operator++ with _Tp = property)' to bool'
A gdzie tam jest niby konwersja na bool [???] Albo to jakieś bajki albo zapomniałem jak się działa na liście, proszę o radę.

0

A teraz widzisz błąd?

for(std::list<property>::iterator it=kontener.begin() ;
     it!=kontener.end() ,
     it++)
{
  ...
}
0

coś mi wygląda na to, że chcesz skorzystać z map<string,boost::any>!

0

Cholera ten przecinek zamiast średnika czasem mi się zdarza i bardzo trudno to wyłapać [glowa] Dzięki za podpowiedź. Co do mapki, to rzeczywiście będzie wygodniej. Już zmieniłem mój program aby przechowywać wszystko w mapie. W zasadzie kontener jest już gotowy i działa jak należy, jest tylko jeszcze jedna mała rzecz do zrobienia. Mianowicie chciałbym aby metody mojego kontenera modyfikowały nie tylko kopie obiektów zawarte wewnątrz mapy (która jest częścią kontenera) ale żeby oryginalne obiekty także były modyfikowane. Próbowałem zamiast obiektów wrzucać do mapy wskaźniki na nie, ale coś mi nie idzie. Nie jestem pewny czy to jest dobra droga :-/

0

Nie rozumiem sensu tego przedsięwzięcia. Po co komu taki kontener, w dodatku bez iteratorów i nie spełniający wymagań STL'a skoro można używać jednej z kolekcji STL i typu boost::any?

0

Sens istnienia takiego kontenera nie jest tutaj rzeczą najważniejszą [diabel]
Ale mylisz się pisząc, że można to samo zrobić samym stl'em i boost::any
Chodzi o to, aby po wykonaniu takiego kodu:

KontenerNaZmienne kont;

        int mojInt(0);
        double mojDouble(0.);
        std::string mojString("");

        kont.dodaj("integer", mojInt);
        kont.dodaj("dbl", mojDouble);
        kont.dodaj("str", mojString);

        kont.przypisz("integer", "15");
        kont.przypisz("dbl", "45.9");
        kont.przypisz("str", "wacek");

        cout<<mojInt<<endl;
        cout<<mojDouble<<endl;
        cout<<mojString<<endl;

na ekranie pojawiły się nowe wartości zmiennych. Zresztą spróbuj sobie wyświetlić bez znieczulenia zmienną typu boost::any na ekran to się przekonasz o czym piszę :d

0
Wicko napisał(a)

Chodzi o to, aby po wykonaniu takiego kodu:
...
na ekranie pojawiły się nowe wartości zmiennych. Zresztą spróbuj sobie wyświetlić bez znieczulenia zmienną typu boost::any na ekran to się przekonasz o czym piszę :d

zamiast wrzucac KOPIE int'a, double'a i string'a, po prostu wrzuc do kontenera referencje-na-.. pomoze Ci boost::ref

0

Kurde nie ma tam przykładów jak tego używać, a z tego co znalazłem w necie to zamiast odpowiedzieć na moje pytania to zrobiło się jeszcze więcej pytań. Najważniejsze to:
Czy zmodyfikować mapę tak, aby przechowywała obiekty typu reference_wrapper, czy może problem jest o wiele łatwiejszy do rozwiązania i po prostu w funkcji dodającej nowe zmienne do kontenera należy tam zastosować jakoś tą referencję, a ona potem i tak zostanie zapakowana do boost::any? Sorry za takie pytania ale pierwszy raz widzę tą bibliotekę.

0

przyznam, ze nie chce mi sie teraz kompilatora odpalac.. DODAJ jest u Ciebie tempplate'em wiec chyba nawet nic ie musisz zmieniac, poza:

        kont.dodaj("integer", mojInt);
        kont.dodaj("dbl", mojDouble);
        kont.dodaj("str", mojString);

na

        kont.dodaj("integer", boost:ref(mojInt));
        kont.dodaj("dbl", boost:ref(mojDouble));
        kont.dodaj("str", boost:ref(mojString));
0

Niestety samo przekazanie takiej referencji nie wystarczy, bo potem w funkcji PRZYPISZ muszę to jeszcze wygrzebać i rozpoznać typ, abym mógł robić działania na tych zmiennych. Ale już powoli zbliżam się do końca [browar]

Edit: No właśnie i teraz mam kolejny problem, bo jak do mapy wrzucam referencje, to moje funkcje odpowiedzialne za rozpoznawanie typu zmiennej w boost::any już nie działają :-/ Dodatkowo, o ile konwersja z boost::any na int, double czy string jest prosta za pomocą any_cast, to na boost::reference_wrapper<> już raczej nie dam rady tego przekonwertować. Próbowałem napisać funkcję do tego:

bool is_int_ref(const boost::any & operand)
    {
        try{
            any_cast<boost::reference_wrapper<int> >(operand);
            return true;
        }
        catch(const boost::bad_any_cast &)
        {
            return false;
        }
    }

Ale za każdym razem rzuca wyjątkiem, tak jakby w operandzie nie było boost::reference_wrapper<> :-/

0

Napiszę nowego posta, bo te problemy z poprzedniego są już nieaktualne. Za pomocą any_cast można jednak ładnie rzutować na wrappery, więc problemu nie ma. Co więcej, udało mi się nawet obejść zapis:

dodaj("int",boost::ref(myInt))

i mogę podawać normalnie wartości, a templacie dodającym do kontenera zamieniam to dopiero na referencje, czyli jest cacy. Ogólnie to wszystko już działa tak jak chciałem, ale nigdy nie jest tak, żeby nie mogło być lepiej.
Do tej pory wewnątrz klasy mojego kontenerka miałem trzy funkcje, sprawdzające czy element mapy jest wraperem inta, double, czy może stringa. Pomyślałem sobie, że trochę niewygodne jest pisanie funkcji sprawdzających dla wszystkich typów, i postanowiłem użyć klas pomocniczych dla klasy reference_wrapper, czyli is_reference_wrapper i unwrap_reference. Wydaje się, że za ich pomocą można stworzyć jedną uniwersalną funkcję rozpoznającą typ wrappera. Koncepcja wygląda następująco:

  1. Znajduję interesujący mnie element mapy
  2. Za pomocą is_reference_wrapper sprawdzam czy element jest wrapperem:
if(boost::is_reference_wrapper<container.find(namedVariable)>::value)

tu pojawia się pierwszy problem, wartość w nawiasikach <> jest static const, w związku z czym nie mogę wstawiać tam zmiennych :-/ Wie ktoś jak to obejść?
3. Jeśli powyższy test wypadł pozytywnie, przystępuję do rzutowania na odpowiedni typ:

try{
                        (any_cast<boost::reference_wrapper<boost::unwrap_reference<container.find(namedVariable)->second)>::type> >(container.find(namedVariable)->second));
                    }

Tu prawdopodobnie będzie to samo co wyżej, czyli nie da rady wstawić zmiennych w nawiasiki <> [glowa]
4. Jeśli wszystko pójdzie dobrze, to na końcu dopiero, w zależności od typu na jaki rzutowałem, wykonuję operację zmiany wartości:

wczesniej_rzutowana_referencja.get()=atoi(vaLue.c_str());
wczesniej_rzutowana_referencja.get()=atof(vaLue.c_str());
wczesniej_rzutowana_referencja.get()=vaLue.c_str();
//itd...

Myślicie że dobry plan?

0

boost::is_reference_wrapper jest KLASA a nie funkcja. nawiasiki <> to parametry template'a, musz abyc STALE podczas kompilacji. boost::is_reference_wrapper sluzy do sprawdzania, czy 'T' template'a jest refwrapper'em..
innymislowy, jedno z prawidlowych uzyc to:

template<typename T> void Kontener::dodaj(string klucz, T wartosc)
{
    if(boost::is_reference_wrapper<T>::value)
        cout << "przekazana zmienna jest refwrapperem" << endl;
}
0

Hmm, no rzeczywiście [glowa] To cały mój plan nie wypali, musiałbym stworzyć drugą mapę na typy danych, i do jednej wpisywać dane, do drugiej typ (odkodowany za pomocą boost::unwrap_reference) i potem po wspólnym kluczu sprawdzać który wraper jest jakiego typu :-/ Przynajmniej takie rozwiązanie mi teraz przychodzi do głowy, ale w założeniach projektu mam żeby był tylko jeden kontener, więc druga mapka odpada. A może jest jakiś inny sposób żeby wyciągnąć typ referencji z wrappera?

0
Wicko napisał(a)

A może jest jakiś inny sposób żeby wyciągnąć typ referencji z wrappera?

sam sobie odpowiedziales w poprzednim poscie:

Wicko napisał(a)

boost::unwrap_reference<T>::type

0

Tak, ale to mogę zrobić tylko w szablonie, czyli w tej funkcji gdzie dodaję zmienne do kontenera. A ja potrzebuję zidentyfikować rodzaj wrappera w funkcji przypisującej zmiennym wartości. Tam operuję już na mapie, czyli nie mogę wstawić odwołań do mapy między nawiasiki <>. No chyba że coś źle zrozumiałem, ale według dokumentacji sytuacja jest analogiczna jak z is_reference_wrapper<>.
Chyba że bym dopisał mały kawałek do funkcji przypisującej dane, zawierający szablon funkcji sprawdzającej rodzaj wrappera [???] Ale nie jestem pewien czy można takie wewnętrzne szablony tworzyć, które w dodatku będą nijako obsługiwane automatycznie w zależności od tego co aktualnie jest wyjmowane z mapy. Trochę się zamotałem, nie wiem czy jeszcze czaisz o co mi chodzi [green]

0

to bedzie ciezke. jak cos wrzuciles do ANY i chcesz generalnej informacji o typie, to jedyna rada to:

const std::type_info & any::type() const;

..czyli typeid.. z tym dowiesz sie dokladnie co jest trzymane, tylko to nie jest takie prostolinijne jakby sie chcialo:)
http://www.boost.org/doc/html/boost/any.html

0

No właśnie tego używam do rozpoznania typu ukrytego w any, ale wiąże się to ze sprawdzaniem po kolei wszystkich typów jakie mogą znaleźć się w kontenerze. Ale skoro nie ma innej rady to zostanę przy tym. W zasadzie to tak sobie teraz myślę, że nawet jakbym rozpoznał typ za pomocą jednej uniwersalnej funkcji to i tak modyfikacja zawartości będzie odbywać się różnym sposobem dla różnych typów danych, bo np. dla inta będzie trzeba użyć atoi, dla double atof, a stringa po prostu można przyrównać, więc oddzielne funkcje dla różnych typów wydają się nie do przeskoczenia. Dziękuję za rady, temat można chyba zamknąć.

0

Witam a ja mam jedno pytanie też do podobnego problemu.
Stworzyłem sobie bowiem ów "kontener" i wszystko działało elegancko pięknie. Przeciążyłem więc operator [] zeby można było wypisywać jak za pomocą normalnej tablicy. No i teraz chciałem sie zapytać w jaki sposób mam przeciążyć operator = zeby działał mi taki mniej więcej kod:

kontener[0] = 11;

problem polega na tym iz kontener[0] zwraca to co jest w kontenerze i wtedy nie da sie odpowiednio przeciazyc operatora = bo z lewej strony jest wartosc a nie zmienna. Problem polega na tym aby zdefiniowac kiedy porzeciazenie ma wystapic a kiedy nie albo przeciazyc []= czego prawdopodobnie o ile sie orientuje nie da sie zrobic</cpp>

0

ale możesz zrobić operator[] który zwraca twój typ, np taki:

template<class T>
class my_wrapper {
   T* ptr;
   my_wrapper(T* real_value_somewhere) : ptr(real_value_somewhere) { }

   const T& operator=(const T&) { *ptr = T; }

   operator T&() { return *ptr; }
   };

i niech operator[] nie zwraca value, tylko: my_wrapper(&value);

0

ranides - albo niezrozumialem problemu mpawla, albo jego przypadek jest muchos-prostszos niz ten watek omawiał:)

mpawel007 napisał(a)

kontener[0] zwraca to co jest w kontenerze i wtedy nie da sie odpowiednio przeciazyc operatora = bo z lewej strony jest wartosc a nie zmienna. Problem polega na tym aby zdefiniowac kiedy porzeciazenie ma wystapic a kiedy nie albo przeciazyc []= czego prawdopodobnie o ile sie orientuje nie da sie zrobic

tak, []= nie istnieje

niech kontener[.] zamiast WARTOSCI zwraca REFERENCJE na swoj element.
wtedy, przy probie wykonania kontener[n] = wartosc wywola sie operator= ELEMENTU KONTENERA a nie zaden operator kontenera! pomyliles co-jest-czyim-operatorem:) dlatego wlasnie []= nie istnieje!

0

No dzieki wielkie, bez waszej pomocy nie wpadłbym aby użyć referencji ;P.

0

No wysoce prawdopodobne, że jest tak jak mówisz, bo całego wątku nie czytałem - widzę tylko myśl o "przeciążeniu []=", więc napisałem, coby zrobić, żeby operator= faktycznie "przeładować" ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1