Template et include

a marqué ce sujet comme résolu.

Bonjour,

Aujourd’hui je tombe sur une petite chose que je ne m’explique pas très bien…

Fichier strings.hpp

1
2
3
4
5
6
7
template<int N> inline std::string format (const std::string& fmt) { return fmt; }

template<int N=1, class A, class... L> inline std::string format (const std::string& fmt, const A& arg, L... others) {
return format<N+1>(
boost::replace_all_copy(fmt, '%' + to_string(N), to_string(arg)),
std::forward<L>(others)...);
}

Que fait ce code ? C’est un genre de mini sprintf++. IL s’utilise comme ceci:

1
string message = format("Bonjour, je m'appelle %1 et j'ai %2 ans", "Bob", 28);

ET on peut passer en paramètre n’importe quoi, pourvu qu’il existe une version de std::string to_string(const T&);. IL en existe en standard C++17 ou sinon comme pour moi avec MinGW32 (GCC 5.3) où je ne les ai pas, ça se définit facilement. Jusque là ça fonctionne super bien.

Là où je ne comprends plus, c’est à partir d’ici. J’ai un second fichier avec ceci:

quelquechose.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "strings.hpp"

...

// Une nouvelle surcharge de la fameuse fonction to_string, dont je n'ai besoin que localement
static inline string to_string (const pair<int,int>& p) {
...
}

...

int a=1, b=2, c=3, d=4;
string message = format("%1 et %2.", make_pair(a,b), make_pair(c,d));

ET là, ça ne compile pas. Je me ramasse la tonne de messages d’erreur pour me dire qu’il ne peut pas instancier ma fonction template format parce qu’il ne trouve pas la fonction std::string to_string(const std::pair<int,int>&);. IL m’énumère toutes les autres version existantes comme potentielles candidates, mais c’est comme si celle que je venais de définir localement n’existait pas, elle n’est pas listée.

Juste pour voir, j’enlève static, j’enlève inline dans la définition de ma surcharge de to_string; ça ne passe pas mieux.

Je tente aussi de définir ma surcharge avant l’include de strings.hpp. Ah ben non, ça veut pas non plus.

J’ajoute le prototype dans strings.hpp et là, bizarrement, ça marche. LE prototype est défini en inline et la fonction elle-même dans mon fichier .cpp est toujours static inline; et ça compile.

Pourquoi il a absolument besoin du prototype dans strings.hpp ? ET comment ça se fait que ça marche même avec static inline ? ET pourquoi même si je définis la fonction avant l’include, ça ne passe pas ?

Je croyais qu’il ne vérifiait l’existance de la bonne version de to_string qu’au moment de l’instantiation, donc dans mon .cpp et après que’lle ait été bien définie ?

Ca m’aurais arrangé de ne pas toucher strings.hpp vu que cette surcharge particulière n’est utilisée que dans ce seul fichier .cpp précisément. J’utilise strings.hpp dans plusieurs projets différents qui n’ont rien à voir les uns avec les autres.

Merci pour vos réponses.

EDIT: orthographe

+0 -0

Le truc c’est que dans ta fonction format, la fonction to_string doit déjà avoir été déclarée plus tôt (avec le bon type en paramètre) pour pouvoir être appelée.

Normalement, déclarer la fonction avant l’include devrait fonctionner. Je vois pas pourquoi ça ne compilerait pas ici.

À mon avis, une solution plus simple serait de déclarer une fonction to_string avec un paramètre template afin qu’elle accepte n’importe quelle type (plus besoin de déclarer chaque surcharge). Ensuite, il suffit de spécialiser la fonction template avec les différents types dont on a besoin.

Par exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// strings.hpp

template<typename T>
inline auto to_string(T & value)
{
    // comportement par défaut
    static_assert(std::is_arithmetic_v<T>);
    return std::to_string(value);
}

// ici : la fonction format
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// main.cpp

#include "strings.hpp"

template<>
inline auto to_string(const std::pair<int, int> & value)
{
    return std::to_string(value.first) + ":" + std::to_string(value.second);
}

int main()
{
    int a=1, b=2, c=3, d=4;
    std::string message = format("%1 et %2.", std::make_pair(a,b), std::make_pair(c,d));
}
+0 -0

J’avais commencé avec des fonctions template et des spécialisations, j’avais arrêté pour revenir à des fonctions normales parce que je trouvais que le parseur était passablement con. ET aussi parce que j’avais l’impression que ça ne m’apportais absolument rien du tout par rapport aux fonctions normales.

Il n’est pas capable de construire une string à partir de const char* alors qu’habituellement il le fait implicitement, et pire, il n’est pas capable non plus de faire des conversions basiques qui sont censées être automatiques même entre des types identiques genre size_t vers unsigned int vers unsigned long vers unsigned long int.... Du coup ça me faisait 36 spécialisations idiotes pour être sûr de couvrir tous les cas normaux (avec des jolies macros). ET en bonus j’ai deux versions de format, une pour string et l’autre pour wstring.

Du coup passer par une fonction template et des spécialisations est la seule bonne solution ?

Merci.

+0 -0

Olybri, aucun rapport, c’est un template, donc déclarer la fonction avant ou après ne change rien (c’est avant l’instanciation où c’est nécessaire).

QuentinC, ton problème est que to_string n’est pas un template, mais juste une liste de surcharges donc tu n’as pas le droit (undefined behaviour) de la surcharger avec un type perso. source

Pour le coup, il faut probablement ajouter un "using std::to_string;" au début pour permettre l’ADL (comme c’est fait généralement avec swap)

edit: tu as quelle erreur d’ailleurs ? car je viens de faire un test, et je n’en ai pas (j’ai remplacé la ligne à la fonction boost par un simple std::cout)

+0 -0

J’ai pas l’impression que se soit une surcharge de std::to_string, elle n’est pas définit dans ne namespace std.

Par contre, le fait de mettre le prototype avant string.hpp devrait fonctionner. La seule raison qui empêcherait cela est qu’un include avant strings.hpp inclut lui aussi strings.hpp.

L’ordre de déclaration des fonctions à son importance. Lorsque le compilateur voit l’utilisation d’une fonction f, il va considérer comme potentiel candidates toutes les fonctions f déclarées jusque-là (quel que soit le namespace pour appliquer les règles d’ADL, à mois qu’on le précise).

Si une autre fonction f est déclarée après cet usage, elle sera ignorée sur ce point d’appel, sauf

  • les surcharges de f dans un namespace N dépendant en paramètre d’un type dans N et dont le namespace d’appel de f n’est pas explicitement nommé¹ (ADL).
  • les spécialisations de fonction.

¹ Le namespace dans lequel se trouve l’appel n’a aucune importance.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
namespace B{
class S{};
}

namespace A
{
  class S {};
  template<class T>int foo(T){ return 1; }
}

using namespace A;

template<class T> int f1(T x) { return foo(x); }
template<class T> int f2(T x) { return A::foo(x); }

namespace A
{
  template<> int foo<char>(char){ return 2; }
  int foo(int){ return 2; }
  int foo(S){ return 2; }
  int foo(B::S){ return 2; }
}

template<class T> int g1(T x) { return foo(x); }
template<class T> int g2(T x) { return A::foo(x); }

#include <iostream>

int main()
{
  std::cout << f1(1) << '\n'; // 1
  std::cout << g1(1) << '\n'; // 2
  std::cout << f2(1) << '\n'; // 1
  std::cout << g2(1) << '\n'; // 2
  std::cout << '\n';
  std::cout << f1('a') << '\n'; // 2
  std::cout << g1('a') << '\n'; // 2
  std::cout << f2('a') << '\n'; // 2
  std::cout << g2('a') << '\n'; // 2
  std::cout << '\n';
  std::cout << f1(A::S{}) << '\n'; // 2
  std::cout << g1(A::S{}) << '\n'; // 2
  std::cout << f2(A::S{}) << '\n'; // 1
  std::cout << g2(A::S{}) << '\n'; // 2
  std::cout << '\n';
  std::cout << f1(B::S{}) << '\n'; // 1
  std::cout << g1(B::S{}) << '\n'; // 2
  std::cout << f2(B::S{}) << '\n'; // 1
  std::cout << g2(B::S{}) << '\n'; // 2
}

EDIT: ratage dans les sorties.

+2 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte