Portál AbcLinuxu, 13. června 2025 16:55


Dotaz: C++ Template a návratový typ

13.7.2018 21:16 Pavel
C++ Template a návratový typ
Přečteno: 2361×
Odpovědět | Admin
Zdravím, mám né zrovna šikovně navrženou část aplikace v C++ a pokouším se to zjednodušit - nejspíš pomocí templatů, které jsem ale už dost zapomněl, takže bych tady chtěl poprosit o radu...

Mam data dost ošlivě uložené ve třídě ze které je získávám jako pole pomocí metod readXY podle datových typů. Původně byly 2, teď už jich je hromada:

Class Data
{
public:
...
std::vector<u8> readU8(Info i);
std::vector<float> readFloat(Info i);
std::vector<std::vector<float>> readFloatVector(Info i);
...
}
Ve třídě Info je hromada informací podle kterých ty data získávám + jejich datový typ uložený jako enum class

enum class DatovyTyp : u8 {U8,Float,FloatVector,...}
class Info
{
public:
...
std::string jmeno;
DatovyTyp typ;
...
}
Celé použití je dost nešikovné, protože když chci získat všechny data a pak je zase dál použít, musím mít IF pro každý datový typ: (navazující metody už jsou teplate nebo přetížené, takže umí pracovat se všemi datovými typy, které potřebuju)

Data d(...);
for(...)
{
  Info i(...);
  if(i.typ == DatovyTyp::Float)
  {
    std::vector<float> tmp = d.readFloat(i);
    nějakáPřetíženáFunkceNeboTemplate(tmp,...);
  }
  else if(i.typ == VariableType::U8)
  {
    nějakáPřetíženáFunkceNeboTemplate(d.readU8(i),...);
  }
}
Použití template ve smyslu:

template <typename T>
    T read(Info i)
{
...
mě sice sjednotí všechny readXY do jedné šablony, ale pořád budu muset mít IF pro každý datový typ

if(i.typ == DatovyTyp::Float)
{
  std::vector<float> tmp = d.read<float>(i);
  nějakáPřetíženáFunkceNeboTemplate(tmp,...);
}
else if(i.typ == VariableType::U8)
{
  nějakáPřetíženáFunkceNeboTemplate(d.read<u8>(i),...);
}
Chtěl bych idálně mít nějaký kouzelný template, který vrací datový typ podle toho enum class DatovyTyp - což pokud vím nejde.

Takže se ptám, jestli existuje nějaká možnost jak uložit informaci o datovém typu tak, abych to potom mohl použít k určení návratového datového typu v template? Zatím jsem nic nenašel a co se pamatuju, tak tohle template neumožňují - existuje nějaká možnost jak to obejít?

Nebo jak to celé upravit nějak jinak, abych se vyhnul IF pro každý datový typ?

Díky.
Nástroje: Začni sledovat (1) ?Zašle upozornění na váš email při vložení nového komentáře.

Odpovědi

13.7.2018 23:30 MadCatX
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
Odpovědět | | Sbalit | Link | Blokovat | Admin
Jestli to chápu správně, potřebuješ se rozhodnout za běhu, jaká data chceš z té třídy Data získat. To se čistě templaty vyřešit nedá, protože templaty v C++ se řeší pouze při překladu. Nějaké runtimové logice s větvením se tedy nevyhneš. Dá se to ale částečně zjednodušit tím, že logiku, která za běhu zjistí požadovaný datový typ a podle toho zavolá příslušnou obsluhu přesuneš do jedné flexibilní funkce. Nějaký nástin, jak na to můžeš najít třeba tady: https://pastebin.com/XUvcSJUr. Vtip je v tom, že to, co se předává dispatcheru jako templatový parameter P lze naimplemetovat libovolně, zatímco dispatcher bude vždy jen jeden.

Dalo by se to samozřejmě rozšířit i pro případy, že by měl dispatcher něco vracet apod. ale princip by byl stejný...
13.7.2018 23:46 MadCatX
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
Šikovnější verze, která se neomezuje jen na funkci read():

https://pastebin.com/5GuZk4m5
16.7.2018 10:32 Pavel
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
Díky moc, něco takového jsem hledal... jen jsem doufal v trochu jednodušší použití. Určitě to v nějaké podobě použiju, ale má to bejt zároveň rozhraní knihovny kterou budou používat i lidi pro který je šablona sprostý slovo - takže moc nepředpokládám, že by si někdo implementoval P.

Po delší době jsem zjistil, kolik jsem toho a zapomněl a jak moc mi ujíždí vlak... každopádně ještě jednou díky.

16.7.2018 11:59 MadCatX
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
P, která s budou používat nejčastěji můžeš implementovat ty jako autor rozhraní s tím, že coder monkey prostě zavolá nějaký wrapper okolo dispatcheru a nebude řešit, co se vevnitř děje. std::variant v kódu není nejspíš proto, že je to věcička až z C++17 a starší překladače to nezbaští.
16.7.2018 13:19 Pavel
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
Ano - přesně tak jsem to myslel - že udělám dvě nejčastější použití a celé to zabalím...

"std::variant v kódu není nejspíš proto, že je to věcička až z C++17 a starší překladače to nezbaští" ->

Teď nevím co tím je myšleno... původně se ve třídě Data používal boost::any, který je v boost od 2007 (boost 1.34) stejně jako boost::variant. Spíš jsem se divil, že byl použit any místo variant, který by byl asi vhodnější (má daný výčet povolených typů, do any můžu uložit cokoliv) - ale zas takový rozdíl v nich nevidím. Stejně použití variant / any ať už boost nebo std v tomhle případě nic neřeší - stejně budu znát datový typ, který z toho chci vytáhnout, až za běhu - je jedno, jestli použiju vlastní template nebo std::get<float>(variant) ... jestli mi teda zase něco neuniklo ;-)
14.7.2018 10:18 Sten
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
Odpovědět | | Sbalit | Link | Blokovat | Admin
Jestli to chápu dobře, tak chcete něco jako std::variant? Tam se to řeší přes visitor patern.
14.7.2018 11:44 MadCatX
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
To jsem si původně myslel taky ale z dotazu není jasné, zda obsahuje třída Data data vždy jen jednoho typu nebo zda je možné zavolat d.readFloat() a d.readU8() na jedné instanci té třídy. To by se variantem myslím řešit nedalo. Aspoň trochu flexibilní logika, jak řešit ten druhý případ by mohla vypadat třeba takto:
#include <iostream>
#include <vector>

enum class TypeID {
	String,
	Float,
	Integer
};

template <TypeID>
struct TypeIdentifier {
};

template <>
struct TypeIdentifier<TypeID::String> {
	typedef std::string type;
};

template <>
struct TypeIdentifier<TypeID::Float> {
	typedef float type;
};

template <>
struct TypeIdentifier<TypeID::Integer> {
	typedef int32_t type;
};

struct Data {
	template <typename T>
	std::vector<T> read();
};


template <>
std::vector<std::string> Data::read()
{
	return std::vector<std::string>{ "Zero", "One", "Two" };
}

template <>
std::vector<float> Data::read()
{
	return std::vector<float>{ 0.1, 0.2, 0.3 };
}

template <>
std::vector<int32_t> Data::read()
{
	return std::vector<int32_t>{ 10, 20, 30 };
}

template <typename T>
void print(const T &t)
{
	for (auto && i : t)
		std::cout << i << " ";
	std::cout << "\n";
}

template <typename T>
void printReverse(const T &t)
{
	for (auto it = t.rbegin(); it != t.rend(); it++)
		std::cout << *it << " ";
	std::cout << "\n";
}

template <template <typename> class P, typename S, typename... Args>
void dispatcher(const TypeID id, S &s, Args... args)
{
	switch (id) {
	case TypeID::String:
		P<TypeIdentifier<TypeID::String>::type>::call(s, std::forward<Args>(args)...); break;
	case TypeID::Float:
		P<TypeIdentifier<TypeID::Float>::type>::call(s, std::forward<Args>(args)...); break;
	case TypeID::Integer:
		P<TypeIdentifier<TypeID::Integer>::type>::call(s, std::forward<Args>(args)...); break;
	}
}

template <typename T>
struct Proc {
	static void call(Data &d)
	{
		auto v = d.read<T>();
		print(v);
	}
};

template <>
struct Proc<int32_t> {
	static void call(Data &d)
	{
		std::cout << "Specialization for int32_t\n";

		auto v = d.read<int32_t>();
		print(v);
	}
};

template <typename T>
struct ProcTwo {
	template <typename... Args>
	static void call(Data &d, Args ...)
	{
		auto v = d.read<T>();
		printReverse(v);
	}
};

template <>
struct ProcTwo<float> {
	static void call(Data &d, int i)
	{
		std::cout << "Specialization for float: " << i << "\n";

		auto v = d.read<float>();
		printReverse(v);
	}

	template <typename... Args>
	static void call(Data &, Args...)
	{
		throw std::runtime_error("Function called with invalid parameters");
	}
};

int main()
{
	Data d;

	dispatcher<Proc>(TypeID::String, d);
	dispatcher<Proc>(TypeID::Float, d);
	dispatcher<Proc>(TypeID::Integer, d);

	dispatcher<ProcTwo>(TypeID::String, d);
	dispatcher<ProcTwo>(TypeID::Float, d, 66);
	//dispatcher<ProcTwo>(TypeID::Float, d);    /* Throws at runtime */
	dispatcher<ProcTwo>(TypeID::Integer, d);

	return 0;
}
16.7.2018 10:39 Pavel
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
Třída Data právě obsahuje vždy data víc typů... původně byly interně uloženy jako boost::any (už se nepamatuju proč se nepoužil variant), ale kvůli rychlosti a velikosti se to změnilo na dost ošlivej bastl. I kdyby se použil std::variant, tak by to zrovna v tomhle ničemu nepomohlo (jinde jo, takže se o tom čas od času uvažuje)...
14.7.2018 12:29 Kit | skóre: 45 | Brno
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
Odpovědět | | Sbalit | Link | Blokovat | Admin
Co znamená "získat data ze třídy"? Co takhle raději místo toho "zpracovat data v objektu"? Tím se hromada zmíněných problémů vyřeší, protože ify nebudou potřebné.
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
14.7.2018 22:02 zvol
Rozbalit Rozbalit vše Re: C++ Template a návratový typ
Odpovědět | | Sbalit | Link | Blokovat | Admin
Můžete tím enumem "indexovat" variadický template.

ideone.com/w1CLnF

Ukázka:
template<int N, class Head, class... Tail>
struct Dispatch {
  static void dispatch(const Info& i, const Data& d) {
    if (i.typ == N)
      perform<Head>(i, d);
    else
      Dispatch<N+1, Tail...>::dispatch(i, d);
  }
};

Založit nové vláknoNahoru

Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.