Właśnie oglądałem Stephan T. Lavavej w CppCon 2018
programie „Class Template Argument Deduction”, gdzie w pewnym momencie mimochodem mówi:
W C ++ informacje prawie nigdy nie płyną wstecz ... Musiałem powiedzieć „prawie”, ponieważ jest jeden lub dwa przypadki, prawdopodobnie więcej, ale bardzo niewiele .
Pomimo próby ustalenia, do których przypadków może się odnosić, nie mogłem nic wymyślić. Stąd pytanie:
W jakich przypadkach standard C ++ 17 nakazuje, aby informacje typu były propagowane wstecz?
c++
types
language-lawyer
c++17
type-deduction
Massimiliano
źródło
źródło
Odpowiedzi:
Oto co najmniej jeden przypadek:
struct foo { template<class T> operator T() const { std::cout << sizeof(T) << "\n"; return {}; } };
jeśli to zrobisz
foo f; int x = f; double y = f;
, informacje o typie będą płynąć „wstecz”, aby dowiedzieć się, coT
jest w środkuoperator T
.Możesz użyć tego w bardziej zaawansowany sposób:
template<class T> struct tag_t {using type=T;}; template<class F> struct deduce_return_t { F f; template<class T> operator T()&&{ return std::forward<F>(f)(tag_t<T>{}); } }; template<class F> deduce_return_t(F&&)->deduce_return_t<F>; template<class...Args> auto construct_from( Args&&... args ) { return deduce_return_t{ [&](auto ret){ using R=typename decltype(ret)::type; return R{ std::forward<Args>(args)... }; }}; }
więc teraz mogę to zrobić
std::vector<int> v = construct_from( 1, 2, 3 );
i to działa.
Oczywiście, dlaczego po prostu nie zrobić
{1,2,3}
? Cóż,{1,2,3}
to nie jest wyrażenie.std::vector<std::vector<int>> v; v.emplace_back( construct_from(1,2,3) );
co, co prawda, wymaga nieco więcej magii: przykład na żywo . (Muszę dokonać deduce return, wykonać sprawdzenie SFINAE F, następnie uczynić F przyjaznym dla SFINAE i muszę zablokować std :: initializer_list w operatorze deduce_return_t T.)
źródło
&&
Kwalifikator naoperator T()
to świetny akcent; pomaga uniknąć słabej interakcji zauto
, powodując błąd kompilacji, jeśliauto
zostanie tu niewłaściwie użyty.Stephan T. Lavavej wyjaśnił sprawę, o której mówił w tweecie :
możemy zobaczyć przykłady tego na stronie cppreference pod adresem Adres przeciążonej funkcji , z wyjątkiem kilku poniżej:
int f(int) { return 1; } int f(double) { return 2; } void g( int(&f1)(int), int(*f2)(double) ) {} int main(){ g(f, f); // selects int f(int) for the 1st argument // and int f(double) for the second auto foo = []() -> int (*)(int) { return f; // selects int f(int) }; auto p = static_cast<int(*)(int)>(f); // selects int f(int) }
Michael Park dodaje :
i przedstawia ten przykład na żywo :
void overload(int, int) {} void overload(int, int, int) {} template <typename T1, typename T2, typename A1, typename A2> void f(void (*)(T1, T2), A1&&, A2&&) {} template <typename T1, typename T2, typename T3, typename A1, typename A2, typename A3> void f(void (*)(T1, T2, T3), A1&&, A2&&, A3&&) {} int main () { f(&overload, 1, 2); }
które omówię tutaj trochę więcej .
źródło
Uważam, że w statycznym rzutowaniu przeciążonych funkcji przepływ idzie w przeciwnym kierunku, jak w zwykłej rozdzielczości przeciążenia. Myślę, że jeden z nich jest odwrotny.
źródło