web-dev-qa-db-fra.com

Utilisation des arguments de modèle 'void' en C++

Prenons l'exemple minimal suivant:

using Type1 = std::function<void(void)>;

template <typename T>
using Type2 = std::function<void(T)>;

Type1 whyDoesThisWork;
Type2<void> andYetThisDoesNot;

Si le deuxième alias de type, j'obtiens l'erreur "L'argument n'a peut-être pas le type 'void'". (J'ai testé avec Xcode 4.5, Clang/c ++ 11/libc ++, OS X 10.7.)

Je trouve cela curieux: je me serais attendu à ce que Type1 et Type2<void> se comportent de manière identique. Que se passe t-il ici? Et existe-t-il un moyen de réécrire le deuxième alias de type pour que je peux écrire Type2<void> et obtenir std::function<void(void)> au lieu d’une erreur?

Edit Je devrais probablement ajouter que la raison pour laquelle je veux ceci est de permettre quelque chose comme ce qui suit:

template <typename ... T>
using Continuation = std::function<void(T...)>;

auto someFunc = []() -> void {
  printf("I'm returning void!\n");
};

Continuation<decltype(someFunc())> c;

Continuation<decltype(someFunc())> devient Continuation<void> et j'obtiens l'erreur.

19
Nick Hutchinson

La réponse courte est "les modèles ne sont pas des substitutions de chaînes". void f(void) n'a de sens que dans la mesure où il s'agit d'un alias pour void f() en C++, afin d'être rétro-compatible avec C.

La première étape consiste à utiliser des variadics, comme indiqué ailleurs.

La deuxième étape consiste à déterminer comment mapper les fonctions retournant void à ... eh bien, peut-être quelque chose comme std::function<void()> ou peut-être autre chose. Je dis peut-être autre chose parce que contrairement aux autres cas, vous ne pouvez pas appeler std::function<void()> foo; foo( []()->void {} ); - ce n'est pas une vraie continuation.

Quelque chose comme ça peut-être:

template<typename T>
struct Continuation
{
  typedef std::function<void(T)> type;
};

template<>
struct Continuation<void>
{
  typedef std::function<void()> type;
};

puis utilisez-le comme ceci:

auto someFunc = []()->void {};
Continuation<decltype(someFunc())>::type c;

ce qui vous donne le type que vous voulez. Vous pouvez même ajouter une application à la suite:

template<typename T>
struct Continuation
{
  typedef std::function<void(T)> type;

  template<typename func, typename... Args>
  static void Apply( type const& cont, func&& f, Args... args)
  {
    cont( f(args...) );
  }
};

template<>
struct Continuation<void>
{
  typedef std::function<void()> type;
  template<typename func, typename... Args>
  static void Apply( type const& cont, func&& f, Args... args)
  {
    f(args...);
    cont();
  }
};

qui vous permet d’appliquer une continuation à une exécution d’une fonction de manière uniforme si le type entrant est un vide ou s’il s’agit d’un type non-vide.

Cependant, je demanderais "pourquoi voudriez-vous faire ceci"?

12

Je n'ai pas de réponse réelle, seulement ce que j'ai dit dans le commentaire: Vous ne pouvez pas avoir void comme type de fonction, comme dans:

int foo(int, char, void, bool, void, void);     // nonsense!

Je crois que T(void) n’est autorisé qu’en tant que notation de compatibilité pour C (qui distingue les déclarations et prototypes , très différemment du C++, et qui doit pouvoir dire "aucun argument").

Donc, la solution devrait être variadique:

template <typename ...Args> using myType = std::function<void(Args...)>;

De cette façon, vous pouvez correctement avoir pas d’arguments :

myType<> f = []() { std::cout << "Boo\n"; }
12
Kerrek SB

Plusieurs réponses expliquent déjà le raisonnement. Pour ajouter à ces réponses, la spécification dit (C++ 11 §8.3.5 [dcl.func]/4):

Une liste de paramètres composée d'un seul paramètre non nommé de type non dépendant void est équivalent à une liste de paramètres vide. Hormis ce cas particulier, un paramètre ne doit pas avoir le type cv void.

Dans votre exemple Type2, la T dans void(T) est un type dépendant - il dépend d'un paramètre de modèle.

5
James McNellis

Lorsqu'une fonction est déclarée comme prenant un paramètre de type void, comme dans std::function<void(void)>, c'est simplement une façon bizarre de dire qu'elle ne prend aucun paramètre. Mais vous avez déclaré que Type2 était un std::function avec une signature qui ne renvoie rien (void), mais cela prend 1 paramètre. void n'est pas un type pouvant être utilisé en tant que paramètre, c'est simplement une façon de déclarer qu'il n'y a pas de paramètre. Cela ne fonctionne donc pas avec Type2, car cela nécessite un type réel pouvant être utilisé comme paramètre.

3
Benjamin Lindley

Nul peut être interprété comme un paramètre vide si vous le transmettez à une fonction. Vous n'utilisez pas de pointeur vide après tout, donc 

void func (void)

devient

void func ()
0
count0