Je sais que le code suivant ne se compilera pas.
void baz(int i) { }
void baz() { }
class Bar
{
std::function<void()> bazFn;
public:
Bar(std::function<void()> fun = baz) : bazFn(fun){}
};
int main(int argc, char **argv)
{
Bar b;
return 0;
}
Car std::function
est censé ne pas considérer la résolution de surcharge, comme je l'ai lu dans cet autre article .
Je ne comprends pas bien les limites techniques qui ont forcé ce type de solution.
J'ai lu les phases de traduction et templates sur cppreference, mais je ne peux penser à aucun raisonnement pour lequel je n'ai pas trouvé de contre-exemple. Expliqué à un demi-profane (encore nouveau en C++), quoi et pendant quelle étape de la traduction les éléments ci-dessus ne parviennent-ils pas à être compilés?
Cela n'a rien à voir avec les "phases de traduction". Il s'agit uniquement des constructeurs de std::function
.
Voir, std::function<R(Args)>
ne nécessite pas que la fonction donnée soit exactement de type R(Args)
. En particulier, il ne nécessite pas de disposer d'un pointeur de fonction. Il peut prendre n'importe quel type appelable (pointeur de fonction membre, un objet qui a une surcharge de operator()
) tant qu'il est invocable comme si cela prenait Args
paramètres et renvoie quelque chose convertible enR
(ou si R
est void
, il peut retourner n'importe quoi).
Pour ce faire, le constructeur approprié de std::function
Doit être un modèle: template<typename F> function(F f);
. Autrement dit, il peut prendre n'importe quel type de fonction (sous réserve des restrictions ci-dessus).
L'expression baz
représente un ensemble de surcharge. Si vous utilisez cette expression pour appeler l'ensemble de surcharge, c'est très bien. Si vous utilisez cette expression comme paramètre d'une fonction qui prend un pointeur de fonction spécifique, C++ peut réduire la surcharge définie en un seul appel, ce qui le rend très bien.
Cependant, une fois qu'une fonction est un modèle et que vous utilisez la déduction d'argument de modèle pour déterminer ce qu'est ce paramètre, C++ n'a plus la capacité de déterminer la surcharge correcte dans l'ensemble de surcharge. Vous devez donc le spécifier directement.
La résolution de surcharge se produit uniquement lorsque (a) vous appelez le nom d'une fonction/opérateur, ou (b) vous le transformez en un pointeur (vers une fonction ou une fonction membre) avec une signature explicite.
Ni l'un ni l'autre ne se produit ici.
std::function
Prend tout objet compatible avec sa signature. Il ne prend pas spécifiquement un pointeur de fonction. (une lambda n'est pas une fonction std, et une fonction std n'est pas une lambda)
Maintenant, dans mes variantes de fonction homebrew, pour la signature R(Args...)
j'accepte également un argument R(*)(Args...)
(une correspondance exacte) pour exactement cette raison. Mais cela signifie qu'il élève les signatures de "correspondance exacte" au-dessus des signatures "compatibles".
Le problème principal est qu'un ensemble de surcharge n'est pas un objet C++. Vous pouvez nommer un ensemble de surcharge, mais vous ne pouvez pas le faire passer "nativement".
Maintenant, vous pouvez créer un ensemble de pseudo-surcharge d'une fonction comme celle-ci:
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
#define OVERLOADS_OF(...) \
[](auto&&...args) \
RETURNS( __VA_ARGS__(decltype(args)(args)...) )
cela crée un seul objet C++ qui peut effectuer une résolution de surcharge sur un nom de fonction.
En développant les macros, nous obtenons:
[](auto&&...args)
noexcept(noexcept( baz(decltype(args)(args)...) ) )
-> decltype( baz(decltype(args)(args)...) )
{ return baz(decltype(args)(args)...); }
ce qui est ennuyeux à écrire. Une version plus simple, mais un peu moins utile, est ici:
[](auto&&...args)->decltype(auto)
{ return baz(decltype(args)(args)...); }
nous avons un lambda qui prend n'importe quel nombre d'arguments, puis parfait les transmet à baz
.
Ensuite:
class Bar {
std::function<void()> bazFn;
public:
Bar(std::function<void()> fun = OVERLOADS_OF(baz)) : bazFn(fun){}
};
travaux. Nous reportons la résolution de surcharge dans le lambda que nous stockons dans fun
, au lieu de passer fun
un ensemble de surcharge directement (qu'il ne peut pas résoudre).
Il y a eu au moins une proposition pour définir une opération dans le langage C++ qui convertit un nom de fonction en un objet d'ensemble de surcharge. Jusqu'à ce qu'une telle proposition standard soit dans la norme, la macro OVERLOADS_OF
Est utile.
Vous pouvez aller plus loin et prendre en charge la conversion en pointeur de fonction compatible.
struct baz_overloads {
template<class...Ts>
auto operator()(Ts&&...ts)const
RETURNS( baz(std::forward<Ts>(ts)...) );
template<class R, class...Args>
using fptr = R(*)(Args...);
//TODO: SFINAE-friendly support
template<class R, class...Ts>
operator fptr<R,Ts...>() const {
return [](Ts...ts)->R { return baz(std::forward<Ts>(ts)...); };
}
};
mais cela commence à devenir obtus.
#define OVERLOADS_T(...) \
struct { \
template<class...Ts> \
auto operator()(Ts&&...ts)const \
RETURNS( __VA_ARGS__(std::forward<Ts>(ts)...) ); \
\
template<class R, class...Args> \
using fptr = R(*)(Args...); \
\
template<class R, class...Ts> \
operator fptr<R,Ts...>() const { \
return [](Ts...ts)->R { return __VA_ARGS__(std::forward<Ts>(ts)...); }; \
} \
}
Le problème ici est qu'il n'y a rien qui indique au compilateur comment effectuer la fonction de décomposition du pointeur. Si tu as
void baz(int i) { }
void baz() { }
class Bar
{
void (*bazFn)();
public:
Bar(void(*fun)() = baz) : bazFn(fun){}
};
int main(int argc, char **argv)
{
Bar b;
return 0;
}
Ensuite, le code fonctionnerait puisque maintenant le compilateur sait à quelle fonction vous voulez car il y a un type concret auquel vous assignez.
Lorsque vous utilisez std::function
vous appelez son constructeur d'objet fonction qui a la forme
template< class F >
function( F f );
et comme il s'agit d'un modèle, il doit déduire le type de l'objet transmis. étant donné que baz
est une fonction surchargée, aucun type unique ne peut être déduit, la déduction de modèle échoue et vous obtenez une erreur. Vous devez utiliser
Bar(std::function<void()> fun = (void(*)())baz) : bazFn(fun){}
pour forcer un seul type et permettre la déduction.
Au moment où le compilateur décide quelle surcharge passer dans le std::function
constructeur tout ce qu'il sait, c'est que le std::function
le constructeur est conçu pour prendre n'importe quel type. Il n'a pas la possibilité d'essayer les deux surcharges et de constater que le premier ne compile pas mais le second le fait.
La façon de résoudre ce problème est d'indiquer explicitement au compilateur la surcharge que vous souhaitez avec un static_cast
:
Bar(std::function<void()> fun = static_cast<void(*)()>(baz)) : bazFn(fun){}