Różnica między std :: result_of i decltype


100

Mam problemy ze zrozumieniem potrzeby std::result_ofw C ++ 0x. Jeśli dobrze zrozumiałem, result_ofsłuży do uzyskania wynikowego typu wywołania obiektu funkcji z określonymi typami parametrów. Na przykład:

template <typename F, typename Arg>
typename std::result_of<F(Arg)>::type
invoke(F f, Arg a)
{
    return f(a);
}

Naprawdę nie widzę różnicy w następującym kodzie:

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(f(a)) //uses the f parameter
{
    return f(a);
}

lub

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(F()(a)); //"constructs" an F
{
    return f(a);
}

Jedyny problem, jaki widzę w przypadku tych dwóch rozwiązań, polega na tym, że musimy:

  • mieć instancję funktora, aby użyć go w wyrażeniu przekazanym do decltype.
  • znać zdefiniowanego konstruktora dla funktora.

Czy mam rację sądząc, że jedyna różnica między decltypei result_ofpolega na tym, że pierwsza z nich wymaga wyrażenia, a druga nie?

Odpowiedzi:


86

result_ofzostał wprowadzony w Boost , a następnie zawarty w TR1 , a na końcu w C ++ 0x. Dlatego result_ofma tę zaletę, że jest kompatybilny wstecz (z odpowiednią biblioteką).

decltype jest całkowicie nową rzeczą w C ++ 0x, nie ogranicza się tylko do zwracanego typu funkcji i jest funkcją języka.


W każdym razie na gcc 4.5 result_ofjest zaimplementowany pod względem decltype:

  template<typename _Signature>
    class result_of;

  template<typename _Functor, typename... _ArgTypes>
    struct result_of<_Functor(_ArgTypes...)>
    {
      typedef
        decltype( std::declval<_Functor>()(std::declval<_ArgTypes>()...) )
        type;
    };

4
O ile rozumiem, decltypejest brzydszy, ale też potężniejszy. result_ofmoże być używany tylko dla typów, które są wywoływalne i wymaga typów jako argumentów. Na przykład nie możesz result_oftutaj użyć : template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );jeśli argumenty mogą być typami arytmetycznymi (nie ma Ftakiej funkcji , którą możesz zdefiniować F(T,U)do reprezentowania t+u. Dla typów zdefiniowanych przez użytkownika możesz. W ten sam sposób (tak naprawdę nie bawiłem się). że wywołania metod result_of
składowych

2
Jedna uwaga, decltype potrzebuje argumentów, aby wywołać funkcję z AFAIK, więc bez result_of <> niewygodne jest pobranie typu zwróconego przez szablon bez polegania na argumentach mających prawidłowe domyślne konstruktory.
Robert Mason

3
@RobertMason: Te argumenty można pobrać za pomocą std::declval, podobnie jak w kodzie, który pokazałem powyżej. Oczywiście to brzydkie :)
kennytm

1
@ DavidRodríguez-dribeas Twój komentarz zawiera niezamknięte nawiasy, które otwierają się znakiem „(jest” :(
Navin

1
Inna uwaga: result_ofi jego typ pomocniczy result_of_tsą przestarzałe od C ++ 17 na korzyść invoke_resulti invoke_result_t, łagodząc niektóre ograniczenia poprzedniego. Są one wymienione na dole strony en.cppreference.com/w/cpp/types/result_of .
sigma

11

Jeśli potrzebujesz czegoś, co nie jest czymś w rodzaju wywołania funkcji, std::result_ofpo prostu nie ma zastosowania. decltype()może podać typ dowolnego wyrażenia.

Jeśli ograniczymy się tylko do różnych sposobów określania typu zwracanego wywołania funkcji (między std::result_of_t<F(Args...)>i decltype(std::declval<F>()(std::declval<Args>()...)), to istnieje różnica.

std::result_of<F(Args...) definiuje się jako:

Jeśli wyrażenie INVOKE (declval<Fn>(), declval<ArgTypes>()...)jest poprawnie sformułowane, gdy jest traktowane jako nieoceniony operand (klauzula 5), ​​typ elementu członkowskiego typedef powinien nazwać typ w decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); przeciwnym razie, nie będzie żadnego typu elementu członkowskiego.

Różnica między result_of<F(Args..)>::typei decltype(std::declval<F>()(std::declval<Args>()...)polega na tym INVOKE. Używanie declval/ decltypebezpośrednio, oprócz tego, że wpisywanie jest nieco dłuższe, jest poprawne tylko wtedy, gdy Fmożna je bezpośrednio wywołać (typ obiektu funkcji lub funkcja lub wskaźnik funkcji). result_ofdodatkowo obsługuje wskaźniki do funkcji składowych i wskaźniki do danych składowych.

Początkowo użycie declval/ decltypegwarantowało wyrażenie przyjazne dla SFINAE, podczas gdy std::result_ofmoże spowodować poważny błąd zamiast niepowodzenia dedukcji. Zostało to poprawione w C ++ 14: std::result_ofjest teraz wymagane, aby było przyjazne dla SFINAE (dzięki temu artykułowi ).

Tak więc zgodny kompilator C ++ 14 std::result_of_t<F(Args...)>jest zdecydowanie lepszy. Jest wyraźniejszy, krótszy i poprawnie obsługuje więcej Fs .


Chyba że używasz go w kontekście, w którym nie chcesz zezwalać na wskaźniki do członków, więc std::result_of_todniesiesz sukces w przypadku, gdy możesz chcieć, aby to się nie udało.

Z wyjątkami. Chociaż obsługuje wskaźniki do członków, result_ofnie będzie działać, jeśli spróbujesz utworzyć wystąpienie nieprawidłowego identyfikatora typu . Obejmowałyby one funkcję zwracającą funkcję lub przyjmującą typy abstrakcyjne według wartości. Dawny.:

template <class F, class R = result_of_t<F()>>
R call(F& f) { return f(); }

int answer() { return 42; }

call(answer); // nope

Prawidłowe użycie byłoby result_of_t<F&()>, ale jest to szczegół, o którym nie musisz pamiętać decltype.


W przypadku typu bez odniesienia Ti funkcji template<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }, czy użycie jest result_of_tprawidłowe?
Zizheng Tai

Ponadto, jeśli argument, do którego przekazujemy, fto a const T, czy powinniśmy użyć result_of_t<F&&(const T&)>?
Zizheng Tai
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.