c++ types language-lawyer c++17 type-deduction

¿Cuándo la información de tipo fluye hacia atrás en C++?



types language-lawyer (3)

Acabo de ver a Stephan T. Lavavej hablar en CppCon 2018 sobre "Deducción de argumentos de plantilla de clase", donde en algún momento dice:

En C ++, la información de tipo casi nunca fluye hacia atrás ... Tenía que decir "casi" porque hay uno o dos casos, posiblemente más pero muy pocos .

A pesar de intentar averiguar a qué casos se refería, no se me ocurrió nada. De ahí la pregunta:

¿En qué casos el estándar C ++ 17 exige que la información de tipo se propague hacia atrás?


Aquí hay al menos un caso:

struct foo { template<class T> operator T() const { std::cout << sizeof(T) << "/n"; return {}; } };

si lo haces foo f; int x = f; double y = f; foo f; int x = f; double y = f; , la información de tipo fluirá "hacia atrás" para averiguar qué T está en el operator T

Puedes usar esto de una manera más avanzada:

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)... }; }}; }

así que ahora puedo hacer

std::vector<int> v = construct_from( 1, 2, 3 );

y funciona.

Por supuesto, ¿por qué no hacer {1,2,3} ? Bueno, {1,2,3} no es una expresión.

std::vector<std::vector<int>> v; v.emplace_back( construct_from(1,2,3) );

lo cual, por cierto, requiere un poco más de magia: ejemplo en vivo . (Tengo que hacer la devolución deducida hacer una comprobación SFINAE de F, luego hacer que la F sea compatible con SFINAE, y tengo que bloquear std :: initializer_list en deduce_return_t operador T.)


Creo que en el lanzamiento estático de funciones sobrecargadas, el flujo va en la dirección opuesta a la resolución de sobrecarga habitual. Así que uno de esos es al revés, supongo.


Stephan T. Lavavej explicó el caso del que estaba hablando en un tweet :

El caso en el que estaba pensando es en dónde puede tomar la dirección de una función sobrecargada / con plantilla y, si se utiliza para inicializar una variable de un tipo específico, eso desactualizará cuál desea. (Hay una lista de lo que desconcierta.)

Podemos ver ejemplos de esto en la página de referencia de cpp en Dirección de función sobrecargada , he exceptuado algunos a continuación:

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 agrega :

No se limita a inicializar un tipo concreto, tampoco. También se podría inferir solo de la cantidad de argumentos.

y proporciona este ejemplo en vivo :

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); }

La cual elaboro un poco más aquí .