C++ konverze na const reference

C++ konverze na const reference
« kdy: 12. 10. 2018, 09:50:17 »
Zdravím.

Stává se mi, že když napíšu něco na blog o programování, odněkud se vyrojí spoustu znalců normy, kteří ji znají zpamětí i po zpátku a hned vědí, co jsem udělal špatně, co řeší lépe std knihovna, se skrytým odkazem "nauč se pořádně normu a standardní knihovnu, než sem něco napíšeš". Upřímě tyto znalce obdivuju, protože mají čas ty stohy dokumentace studovat. A proto je teď moc prosím o pomoc.

Potřebuji toto:

Mám šablonu klasicku
Kód: [Vybrat]
template<typename T> void foo(...). Předpokládá se, že uživatel šablony T explicitně určí, například

Kód: [Vybrat]
foo<int>(...) nebo
Kód: [Vybrat]
foo<int &&>(...)
A nyní hledám v std něco, co mi z T udělá typ vhodný pro předání argumentu. Dle nějakých směrnic se doporučuje aby

  • Pouze jednoduché typy předávat hodnotou
  • Složite typy lépe předávat const referenci
  • Samotné refernece předávat tak jak jsou
  • Pointery jakbysmet

Takže bych to viděl že.
Kód: [Vybrat]

T -> const T &
const T & -> const T &
T & -> T &
T && -> T &&
T * -> T *
scalar<T> -> T

Já si tohle dokážu samozřejmě napsat pomocí částečných specializací. Ale nechce se mi věřit, že ve standardní knihovně nic takového není. Ať hledám jak hledám, nic nemůžu najít. Pomůžete mi? Zase nechci vypadat jako někdo, kdo nemá nastudováno.

PS: Forward to není. Forward je funkce, já potřebuju typ.

Dík.
« Poslední změna: 12. 10. 2018, 09:53:17 od Ondřej Novák »


Danny

Re:C++ konverze na const reference
« Odpověď #1 kdy: 12. 10. 2018, 10:04:39 »
typedef?

lopata

Re:C++ konverze na const reference
« Odpověď #2 kdy: 12. 10. 2018, 10:20:26 »
Dá se použít std::is_ z type traits: https://en.cppreference.com/w/cpp/header/type_traits

JSH

Re:C++ konverze na const reference
« Odpověď #3 kdy: 12. 10. 2018, 10:30:44 »
V první řadě je mi dost podezřelé, proč by ta funkce měla být takhle parametrizovatelná. Co má ta funkce dělat? Nejsem si úplně jistý, co takováhle funkce vůbec může dělat, pokud musí být schopná zpracovat úplně cokoliv. Třeba to jde i jinak a jednodušeji.

Jinak bych si ten problém asi zjednodušil :
- Vykašlal bych se na "Pouze jednoduché typy předávat hodnotou" a všechno předával referencí. Při inlinování to překladač zoptimalizuje a bez inlinování je to tak velká funkce, že se nějaká dereference projeví jen v hodně vzácných případech.
- Zvážil bych, jestli vůbec řešit &&. Co jsem zatím vypozoroval, tak funkce co bere jak lvalue tak rvalue referenci ji akorát někam forwardne. Pro cokoliv jiného už to chce zase dvě přetížené verze, co dělají něco jiného.

Takže bych to viděl na dvě praktické možnosti :
Kód: [Vybrat]
template<typename T> void foo( const T & );
T bude vždycky hodnota. Kdybych tam potřeboval nějak dostat referenci tak použiju std::ref. A T navíc překladač odhadne.
Ve vzácných případech bych použil forwarding referenci (někdy se jí říká univerzální) :
Kód: [Vybrat]
template<typename T> std::enable_if_t<potrebny_fujtajxl> foo( T && );
Když to má sežrat jak lvalue-ref tak rvalue-ref, tak to stejně můžu ledat tak někam forwardnout. A použil bych to jen výjimečně, páč je to obvykle zbytečný overkill.

Ta pravidla spíš doporučení. Pokud můžeš, tak preferuj tohle. Pokud by to mělo přidat kopec práce, tak se to nevyplatí.

Fakt si nedokážu představit, k čemu by se dalo použít to, co chceš. Proto IMO nic takového ani není ve standardní knihovně. Není to natolik potřeba aby se tím někdo zabýval.

JSH

Re:C++ konverze na const reference
« Odpověď #4 kdy: 12. 10. 2018, 10:40:30 »
Ještě aby to nevypadalo, že jenom trollím :

V C++11 je std::decay a v c++14 std::decay_t. Bez toho scalar a && by to šlo udělat jako
Kód: [Vybrat]
template<typename T> void( const std::decay_t<T> & );
V C++20 přibude i remove_cvref, což by pro tvé účely taky mohlo stačit. Decay si prosím pořádně pročti v manuálu. Odstraní to const a reference, ale navrch to dělá i další věci. Fakt bych ti nedoporučoval to používat, pokud jde udělat jinak.


Re:C++ konverze na const reference
« Odpověď #5 kdy: 12. 10. 2018, 10:46:16 »
Tak jistě, ta funkce ty argumenty bude někam forwardovat a aniž bych chtěl zabihat do podrobnosti, je určena k volání nějakých uživatelských lambda funkci. Už jen to kdy by to forwardovalo argumenty do std::function, kam málokdy člověk napíše const Gadget & jako argument, přestože tam pak lze bindnout funkci která to const v argumentaci ma.

Dalším zadrhelem je, že to chci použít v param packu, takže overload použít nejde. Forward taky použít nejde protože typy je třeba znát dopředu a příklad s funkcí je jen pro zjednodušení, nejspíš to bude třída, která bude mít takovou funkci.

Uživatel může použít T, T&, const T &, T && a mělo by se to předat správně s tím, že T chci během zpracování tahat jako referenci. Nevím jestli funguje const T && &, ale mám pocit že ne, proto ta specializace

lopata

Re:C++ konverze na const reference
« Odpověď #6 kdy: 12. 10. 2018, 10:52:58 »
V C++11 je std::decay a v c++14 std::decay_t. Bez toho scalar a && by to šlo udělat jako
Čistější je něco jako std::conditional<std::is_(arithmetic, rvalue_reference...)<T>, foo, bar>. Ale je otázka, jestli to opravdu není předčasná optimalizace.

vrazda

Re:C++ konverze na const reference
« Odpověď #7 kdy: 12. 10. 2018, 10:58:10 »
Pokud chces explicitne specifikovat na interface ruzne chovani pro ruzne typy, muzes pouzit sfinae. std::enable_if a std::enable_if_t

http://coliru.stacked-crooked.com/a/9caf611c4365ede8

JSH

Re:C++ konverze na const reference
« Odpověď #8 kdy: 12. 10. 2018, 11:03:13 »
Tak jistě, ta funkce ty argumenty bude někam forwardovat a aniž bych chtěl zabihat do podrobnosti, je určena k volání nějakých uživatelských lambda funkci. Už jen to kdy by to forwardovalo argumenty do std::function, kam málokdy člověk napíše const Gadget & jako argument, přestože tam pak lze bindnout funkci která to const v argumentaci ma.

Dalším zadrhelem je, že to chci použít v param packu, takže overload použít nejde. Forward taky použít nejde protože typy je třeba znát dopředu a příklad s funkcí je jen pro zjednodušení, nejspíš to bude třída, která bude mít takovou funkci.

Uživatel může použít T, T&, const T &, T && a mělo by se to předat správně s tím, že T chci během zpracování tahat jako referenci. Nevím jestli funguje const T && &, ale mám pocit že ne, proto ta specializace

Tak pokud chceš forwardovat, tak použij forwarding reference a std::forward. Občas je potřeba tu funkci omezit přes enable_if.
Kód: [Vybrat]
template<typename ... Args>
void foo( Args && ... args ) { bar( std::forward<Args>(args)...); }

std::function právě bere přesně typy parametrů a žádné konverze tam nedělá. Pokud už píšeš typy explicitně, tak už je můžeš zrovna napsat tak jak potřebuješ.

To, aby musel uživatel ručně dodávat templatové parametry funkce mi nepřijde jako dobrý nápad. Pokud musí ručně dodávat parametry aby rozlišil lvalue a rvalue reference, tak jsem si zatraceně jistý, že je to blbý nápad.

Pokud nechceš zabíhat do podrobností, tak můžu jenom hádat, že ses pravděpodobně upnul k nějakému nešikovnému způsobu řešení. To, co chceš dělat, mi přijde silně podezřelé.

Re:C++ konverze na const reference
« Odpověď #9 kdy: 12. 10. 2018, 11:05:04 »
Decay je přesný opak

Dám jiný priklad

Kód: [Vybrat]
template<typename T>
void call(T &&fn)

...
call([=]{...})

Ačkoliv vse je podle učebnic a nejefektivnější perfekt forwarding, přesto dost často vidím funkci call specializovanou tak že se T předává hodnotou, což poznám na dvojnasobnem volání konstruktorů capturovanych hodnot.

Ale možná to je Bug v Gcc. Nemám po ruce reprezentativní priklad momentálně

Re:C++ konverze na const reference
« Odpověď #10 kdy: 12. 10. 2018, 11:16:00 »
Šablona s param pakem není řešení. Uvědomuji si že jsem zapomněl zmínit že ta funkce bude nejspíš virtuální (typy argumentů obdrží trida, ve které bude definována)

Ale máš pravdu, mohu se na to vykašlat a nechat to na userovi, když to nedá jako referenci ať se mu to tam kopíruje (ale přijde mi to nefér vůči němu)

JSH

Re:C++ konverze na const reference
« Odpověď #11 kdy: 12. 10. 2018, 12:01:04 »
Citace
Decay je přesný opak.
Ten decay je tam proto, abych pak mohl vždycky ten const a referenci přidat. Abych nikdy nedostal dvojitou referenci.
Šablona s param pakem není řešení. Uvědomuji si že jsem zapomněl zmínit že ta funkce bude nejspíš virtuální (typy argumentů obdrží trida, ve které bude definována)
No ale tohle kompletně mění celý dotaz. :D
A vypadá to spíš jako nějaká interní třída, se kterou by uživatel moc neměl přijít do styku. Koukni se, jak se běžně dělá type-erasure (třeba pro implementaci toho std::function). Možná chceš udělat tohle.
Citace
Ale máš pravdu, mohu se na to vykašlat a nechat to na userovi, když to nedá jako referenci ať se mu to tam kopíruje (ale přijde mi to nefér vůči němu)
Nefér může taky být to, že ať uživatel dělá co chce, bude tam mít referenci. Když už musí absolvovat celý ten opruz s psaním parametrů, tak už tam ten jeden ampersand napsat zvládne. Pokud mi knihovna nedovolí tam napsat přesně to, co chci, tak ať mě to nenutí psát vůbec.

Re:C++ konverze na const reference
« Odpověď #12 kdy: 12. 10. 2018, 14:12:33 »
No já jsem si uvědomil, že je docela problém provést konverzi parameter packu. On ten parameter pack je docela oříšek. Trochu sem si s tím začal hrát tady

http://cpp.sh/26g5x

Příklad skončí kompilační chybou, to je záměr, protože součástí chyby je výsledek. Výsledkem je vygenerování funkce, kde jsou všechny typy vyžadovány s const T &, ačkoliv původní parameter pack to neměl. Je to primitivnější verze. Problém je totiž vlastní konverze a pak použití konvertovaného param paku na vygenerování prototypu funkce. V příkladu se to řeší tak, že se vygeneruje třída, která obdrží konvertovaný parameter pack a v ní je definovaná ta funkce, která se instanciuje v daném prototypu.

Ale že bych tohle někde viděl ve standardní knihovně? :) Vůbec hledal jsem co všechno se dá dělat s param.pakem a ... nic moc, všichni z toho hned dělají tuple. Jenže nenašel jsem, jak z tuple vyrobit prototyp funkce.

JSH

Re:C++ konverze na const reference
« Odpověď #13 kdy: 12. 10. 2018, 14:41:27 »
No já jsem si uvědomil, že je docela problém provést konverzi parameter packu. On ten parameter pack je docela oříšek.
Zkonvertovat jednotlivé parametry packu není až takový problém :
Kód: [Vybrat]
template<typename ... Args>
class X {
  public:
    auto foo(something_t<Args> ... x)
    {
        return bar( x... );
    }
};
Za something_t si dosaď svou transformaci pro jeden typ. Psát takhle obecný kód ale není vůbec sranda. O tomhle i členové c++ komise beze srandy říkají, že to nedávají. A běžně v tom dělají chyby.

Citace
http://cpp.sh/26g5x

Prosím, zkoukni https://www.youtube.com/watch?v=xnqTKD8uD64 Přesně na tenhle typ kódu naráží Sutter v úvodu. Ta tvoje konverze packu je write-only. Je to totálně nečitelné, neudržovatelné a muset to po tobě upravovat, tak si asi uhryžu nohu jak liška v pasti.

Prosím, moc tě prosím. Přestaň hledat ve standardní knihovně rovnák na svůj ohýbák. Evidentně děláš něco moc moc špatně, takže tě to vede k těmhle prasárnám.

Re:C++ konverze na const reference
« Odpověď #14 kdy: 12. 10. 2018, 17:47:35 »
Nepřijde mi, že dělám něco špatně. Zaprvé řeším nějaký problém. A samozřejmě, že bych to mohl napsat zdlouhavě a řešit to třeba v runtime, ale proč? Třeba jsem řešil to, jak používat lambda funkce a benefitovat z toho, že lambda funkce mají vnitřní stav. Ona s tím totiž standardní knihovna moc nepočítá, třeba při práci se std::function mi každá kopie vytváří i kopii vnitřního stavu. Ale s vnitřním stavem se počítá, protože máme přece klíčové slovo mutable.

 Nebo třeba že lambda funkce je vlastně objekt, ale nemá this.

Protože hodně používám různé callbacky a asynchroní volání, které je realizované lambdo, někdy kvůli vnitřnímu stavu musím místo lambdy napsat třídu, a to je zdlouhavé, kód se stěhuje na jiné místo a nepřehledné, není jasné, kudy kód pokračuje v asynchroním zpracování. Je to víc nepřehledné, než pochopit vnitřní stav. Snažil jsem se nějak řešit tento problém. A to proto, že jakkoliv se zeptam na stack overflow, končím pouze u začátečnických dotazů.


Za druhé, ano, neznám všechny zákoutí práce s param packy, protože třeba vím, že konverze lze napsat do kódu pokud funkci volám, ale nevěděl jsem, že to funguje i pokud definuju parametry funkce. Holt se musím učit.

Takže díky za tu diskuzi.