Je viens de regarder Stephan T. Lavavej parler à CppCon 2018
sur "Déduction d'argument de modèle de classe", où à n point il dit accessoirement:
En C++, l'information de type ne coule presque jamais en arrière ... Je devais dire "presque" car il y a un ou deux cas, peut-être plus mais très peu .
En dépit d'essayer de comprendre à quels cas il pourrait faire référence, je n'ai rien pu trouver. D'où la question:
Dans quels cas la norme C++ 17 impose-t-elle que les informations de type se propagent en arrière?
Voici au moins un cas:
struct foo {
template<class T>
operator T() const {
std::cout << sizeof(T) << "\n";
return {};
}
};
si vous faites foo f; int x = f; double y = f;
, les informations de type vont circuler "en arrière" pour déterminer ce que T
se trouve dans operator T
.
Vous pouvez l'utiliser de manière plus avancée:
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)... };
}};
}
alors maintenant je peux faire
std::vector<int> v = construct_from( 1, 2, 3 );
et il fonctionne.
Bien sûr, pourquoi ne pas simplement faire {1,2,3}
? Eh bien, {1,2,3}
n'est pas une expression.
std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );
ce qui, certes, nécessite un peu plus de magie: Exemple en direct . (Je dois faire le retour de déduction faire un chèque SFINAE de F, puis faire le F être amical SFINAE, et Je dois bloquer std :: initializer_list dans l'opérateur déduit_retour T.)
Stephan T. Lavavej a expliqué le cas dont il parlait dans un Tweet :
Le cas auquel je pensais est celui où vous pouvez prendre l’adresse d’une fonction surchargée/basée sur un modèle et si elle est utilisée pour initialiser une variable d’un type spécifique, elle ne sera pas ambiguë. (Il y a une liste de ce qui est ambiguë.)
nous pouvons en voir des exemples depuis page cppreference sur l’adresse de la fonction surchargée , j’en ai excepté quelques-uns ci-dessous:
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)
}
Cela ne se limite pas à l'initialisation d'un type concret non plus. Il pourrait également déduire simplement du nombre d'arguments
et fournit cet exemple en direct :
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);
}
que j'élabore un peu plus ici .
Je crois que dans la distribution statique de fonctions surchargées, le flux va en sens inverse de celui de la résolution de surcharge habituelle. Donc, l’un de ceux-là est à l’arrière, je suppose.