Pourquoi pouvons-nous détecter la présence des valeurs de paramètre par défaut de operator () avec SFINAE, mais pas celles des fonctions libres et des PMF?

Dans le programme ci-dessous, le cas 1 tente d’utiliser un paramètre par défaut via la fonction pointeur-membre. Le cas 2 tente d’utiliser un paramètre par défaut via une référence de fonction. Le cas 3 utilise le paramètre par défaut dans operator() . Les seules assertions intéressantes ici sont celles qui utilisent l’alias can_call_with_one – les autres existent pour prouver l’exactitude de la configuration.

Dans les dernières versions de GCC, Clang et MSVC disponibles, ce programme échoue avec les assertions à argument unique des cas 1 et 2.

Ma question est double:

  1. Ces résultats sont-ils cohérents avec la norme ISO C ++?
  2. Si oui, pourquoi le cas 3 n’échoue-t-il pas?
 #include  #include  struct substitution_failure {}; substitution_failure check(...); template auto check(Pmf pmf, T t, Args&&... args) -> decltype((t.*pmf)(std::forward(args)...))*; template auto check(Fn&& f, Args&&... args) -> decltype(f(std::forward(args)...))*; template using test_result = std::integral_constant<bool, !std::is_same::value >; template auto can_invoke(Ts&&... ts) -> test_result<decltype(check(std::forward(ts)...))>; namespace case_1 { //pointer to member function struct foo { int bar(int, int = 0); }; using can_call_with_one = decltype(can_invoke(&foo::bar, foo{}, 0)); using can_call_with_two = decltype(can_invoke(&foo::bar, foo{}, 0, 0)); using can_call_with_three = decltype(can_invoke(&foo::bar, foo{}, 0, 0, 0)); static_assert(can_call_with_one{}, "case 1 - can't call with one argument"); static_assert(can_call_with_two{}, "case 1 - can't call with twp arguments"); static_assert(!can_call_with_three{}, "case 1 - can call with three arguments"); } namespace case_2 { //function reference int foo(int, int = 0); using can_call_with_one = decltype(can_invoke(foo, 0)); using can_call_with_two = decltype(can_invoke(foo, 0, 0)); using can_call_with_three = decltype(can_invoke(foo, 0, 0, 0)); static_assert(can_call_with_one{}, "case 2 - can't call with one argument"); static_assert(can_call_with_two{}, "case 2 - can't call with two arguments"); static_assert(!can_call_with_three{}, "case 2 - can call with three arguments"); } namespace case_3 { //function object struct foo { int operator()(int, int = 0); }; using can_call_with_one = decltype(can_invoke(foo{}, 0)); using can_call_with_two = decltype(can_invoke(foo{}, 0, 0)); using can_call_with_three = decltype(can_invoke(foo{}, 0, 0, 0)); static_assert(can_call_with_one{}, "case 3 - can't call with one argument"); static_assert(can_call_with_two{}, "case 3 - can't call with two arguments"); static_assert(!can_call_with_three{}, "case 3 - can call with three arguments"); } int main() { return 0; } 

version exécutable

Le trait de type de la fonction ne vient pas avec les informations des parameters par défaut. Si c’était le cas, vous ne pourriez pas affecter un pointeur d’une fonction avec une valeur par défaut telle que:

 void foo(int, int = 0) {...} 

à:

 void(*fp)(int, int); fp = &foo; 

La question est maintenant de savoir si le langage le permet. Les valeurs par défaut pour un paramètre donné doivent-elles également identifier le type d’une fonction? Cela impliquerait que la valeur par défaut du paramètre soit un constexpr et, en tant que tel, limiterait la convivialité des valeurs par défaut. Ainsi, par exemple, les parameters de type const char * ne peuvent pas avoir de valeur par défaut définie en ligne …

Par contre, si le type de la fonction ne véhiculait que l’information selon laquelle un paramètre donné a une valeur par défaut sans connaître la valeur elle-même, le compilateur ne serait pas en mesure de reconstruire la valeur par défaut de la fonction à partir du pointeur. invocation de fonction.