Próbuję uzyskać dostęp do zawartości wariantu. Nie wiem co tam jest, ale na szczęście wariant wie. Pomyślałem więc, że po prostu zapytam wariant, na jakim jest indeksie, a następnie użyję tego indeksu do std::get
jego zawartości.
Ale to się nie kompiluje:
#include <variant>
int main()
{
std::variant<int, float, char> var { 42.0F };
const std::size_t idx = var.index();
auto res = std::get<idx>(var);
return 0;
}
Błąd występuje w std::get
wywołaniu:
error: no matching function for call to ‘get<idx>(std::variant<int, float, char>&)’
auto res = std::get<idx>(var);
^
In file included from /usr/include/c++/8/variant:37,
from main.cpp:1:
/usr/include/c++/8/utility:216:5: note: candidate: ‘template<long unsigned int _Int, class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)’
get(std::pair<_Tp1, _Tp2>& __in) noexcept
^~~
/usr/include/c++/8/utility:216:5: note: template argument deduction/substitution failed:
main.cpp:9:31: error: the value of ‘idx’ is not usable in a constant expression
auto res = std::get<idx>(var);
^
main.cpp:7:15: note: ‘std::size_t idx’ is not const
std::size_t idx = var.index();
^~~
Jak mogę to naprawić?
Odpowiedzi:
Zasadniczo nie możesz.
Napisałeś:
... ale tylko w czasie wykonywania, a nie w czasie kompilacji.
A to oznacza, że twoja
idx
wartość nie jest czasem kompilacji.A to oznacza, że nie możesz używać
get<idx>()
bezpośrednio.Coś, co możesz zrobić, to mieć instrukcję switch; brzydkie, ale zadziałałoby:
To jest raczej brzydkie. Jak sugerują komentarze, możesz również
std::visit()
(co nie różni się bardzo od powyższego kodu, z wyjątkiem używania argumentów szablonów variadic zamiast jawnego) i całkowicie uniknąć przełączania. W przypadku innych podejść opartych na indeksach (nie specyficznych dlastd::variant
) zobacz:Idiom do symulowania parametrów szablonów numerycznych w czasie wykonywania?
źródło
Aby kompilator mógł działać, musi znać wartość
idx
czasu kompilacjistd::get<idx>()
, ponieważ jest używany jako argument szablonu.Pierwsza opcja: jeśli kod ma działać w czasie kompilacji, wykonaj następujące czynności
constexpr
:Działa
std::variant
to, ponieważ jestconstexpr
przyjazne (wszystkie jego konstruktory i metodyconstexpr
).Druga opcja: Jeżeli kod nie jest przeznaczona do pracy w czasie kompilacji, co jest prawdopodobne w przypadku, kompilator nie można wywnioskować, w czasie kompilacji typu
res
, ponieważ może to być trzy różne rzeczy (int
,float
lubchar
). C ++ jest językiem o typie statycznym, a kompilator musi być w stanie wydedukować typauto res = ...
z następującego po nim wyrażenia (tzn. Zawsze musi być tego samego typu).Możesz użyć
std::get<T>
z typem zamiast indeksu, jeśli już wiesz, co to będzie:Zasadniczo należy użyć,
std::holds_alternative
aby sprawdzić, czy wariant zawiera każdy z podanych typów, i obsługiwać je osobno:Alternatywnie możesz użyć
std::visit
. Jest to nieco bardziej skomplikowane: możesz użyć funkcji lambda / templated, która jest niezależna od typu i działa dla wszystkich typów wariantów, lub przekazać funktor z przeciążonym operatorem wywołania:Zobacz szczegóły std :: visit i przykłady.
źródło
Problem polega na tym, że
std::get<idx>(var);
wymaga (dlaidx
) znanej wartości czasu kompilacji.Więc
constexpr
wartośćAle aby zainicjować
idx
takconstexpr
, jakvar
musiało byćconstexpr
źródło
Problem wynika z tworzenia instancji szablonów w czasie kompilacji, podczas gdy otrzymywany indeks jest obliczany w czasie wykonywania. Podobnie typy C ++ są również definiowane w czasie kompilacji, więc nawet w przypadku
auto
deklaracjires
musi mieć konkretny typ, aby program był poprawnie sformułowany. Oznacza to, że nawet bez ograniczenia w szablonie, to, co próbujesz zrobić, jest z natury niemożliwe dla niestałych wyrażeństd::variant
. Jak można to obejść?Po pierwsze, jeśli twój wariant jest rzeczywiście stałym wyrażeniem, kod kompiluje się i działa zgodnie z oczekiwaniami
W przeciwnym razie będziesz musiał użyć ręcznego mechanizmu rozgałęziania
Możesz zdefiniować te gałęzie za pomocą wzorca odwiedzającego, patrz std :: visit .
źródło
Jest to z natury niemożliwe w modelu C ++; rozważać
Który
f
jest nazywany,f<int>
lubf<double>
? Jeśli jest to „oba”, oznacza to, żeg
zawiera gałąź (której nie ma) lub że istnieją dwie wersjeg
(co po prostu popycha problem na jego wywołującego). Zastanów sięf(T,U,V,W)
- gdzie kończy się kompilator?W rzeczywistości istnieje propozycja JIT dla C ++, która pozwoliłaby na takie rzeczy, kompilując te dodatkowe wersje,
f
kiedy są wywoływane, ale jest to bardzo wcześnie.źródło