2016-04-01 1 views
2

Mon code ressemble à ceci:Comment limiter retour foncteur modèle et types de paramètres

template<typename F> 
void printHello(F f) 
{ 
    f("Hello!"); 
} 

int main() { 
    std::string buf; 
    printHello([&buf](const char*msg) { buf += msg; }); 
    printHello([&buf]() { }); 
} 

La question est - comment puis-je limiter le type F d'accepter seulement lambdas qui ont une signature void(const char*), de sorte que la seconde appel à printHello ne pas échouer à un endroit obscur à l'intérieur printHello mais à la place sur la ligne qui appelle printHello incorrectement?

== == EDIT

Je sais que std::function peut le résoudre dans ce cas particulier (est ce que j'utiliser si je voulais vraiment imprimer « bonjour »). Mais std::function est vraiment quelque chose d'autre et a un coût (même si ce petit coût est, à compter d'aujourd'hui, avril 2016, GCC et MSVC ne peut pas optimiser l'appel virtuel). Donc, ma question peut être considérée comme purement académique - existe-t-il un moyen «modèle» de la résoudre?

+3

'auto printHello (F f) -> decltype (f (" "), void())' –

+0

@PiotrSkotnicki Bien que l'erreur ne soit pas nécessairement moins obscure. –

+0

Vous pouvez également utiliser '' à la place d'un modèle ... 'void printHello (std :: fonction f)' –

Répondre

5

sauf si vous utilisez une ancienne bibliothèque standard, std::function aura des optimisations pour les objets de petite fonction (dont le vôtre est un). Vous ne verrez aucune réduction de performance. Les personnes qui vous disent de ne pas utiliser std::function pour des raisons de performances sont les mêmes personnes qui «optimisent» le code avant de mesurer les goulots d'étranglement.

Écrivez le code qui exprime l'intention. SI il devient un goulot d'étranglement de la performance (il ne sera pas) alors regardez-le changer.

J'ai déjà travaillé sur un système de prix financiers à terme. Quelqu'un a décidé qu'il fonctionnait trop lentement (64 cœurs, plusieurs boîtes de serveurs, des centaines de milliers d'algorithmes discrets fonctionnant en parallèle dans un DAG massif). Nous l'avons donc profilé.

Qu'avons-nous trouvé?

Le traitement a pris presque pas de temps du tout. Le programme passait 99% de son temps à convertir des doubles en chaînes et en chaînes à doubler aux limites de l'E/S, où nous devions communiquer avec un bus de messages.

L'utilisation d'un lambda à la place d'un std::function pour les rappels n'aurait fait aucune différence.

Écrivez un code élégant. Exprimez clairement votre intention. Compiler avec optimisations. Marvel que le compilateur fait son travail et transforme vos 100 lignes de C++ en 5 instructions de code machine.

Une démonstration simple:

#include <functional> 

// external function forces an actual function call 
extern void other_func(const char* p); 

// try to force std::function to call polymorphically  
void test_it(const std::function<void(const char*)>& f, const char* p) 
{ 
    f(p); 
} 

int main() 
{ 
    // make our function object 
    auto f = std::function<void(const char*)>([](const char* p) { other_func(p); }); 

    const char* const data[] = { 
     "foo", 
     "bar", 
     "baz" 
    }; 

    // call it in a tight loop 
    for(auto p : data) { 
     test_it(f, p); 
    } 
} 

compilez avec clang pomme, -O2:

Résultat:

.globl _main 
    .align 4, 0x90 
_main:         ## @main 
Lfunc_begin1: 
    .cfi_startproc 
    .cfi_personality 155, ___gxx_personality_v0 
    .cfi_lsda 16, Lexception1 
## BB#0:        ## %_ZNKSt3__18functionIFvPKcEEclES2_.exit.i 

# 
# the normal stack frame stuff... 
# 
    pushq %rbp 
Ltmp13: 
    .cfi_def_cfa_offset 16 
Ltmp14: 
    .cfi_offset %rbp, -16 
    movq %rsp, %rbp 
Ltmp15: 
    .cfi_def_cfa_register %rbp 
    pushq %r15 
    pushq %r14 
    pushq %rbx 
    subq $72, %rsp 
Ltmp16: 
    .cfi_offset %rbx, -40 
Ltmp17: 
    .cfi_offset %r14, -32 
Ltmp18: 
    .cfi_offset %r15, -24 
    movq [email protected](%rip), %rbx 
    movq (%rbx), %rbx 
    movq %rbx, -32(%rbp) 
    leaq -80(%rbp), %r15 
    movq %r15, -48(%rbp) 
# 
# take the address of std::function's vtable... we'll need it (once) 
# 
    leaq __ZTVNSt3__110__function6__funcIZ4mainE3$_0NS_9allocatorIS2_EEFvPKcEEE+16(%rip), %rax 
# 
# here's the tight loop... 
# 
    movq %rax, -80(%rbp) 
    leaq L_.str(%rip), %rdi 
    movq %rdi, -88(%rbp) 
Ltmp3: 
# 
# oh look! std::function's call has been TOTALLY INLINED!! 
# 
    callq __Z10other_funcPKc 
Ltmp4: 
LBB1_2:         ## %_ZNSt3__110__function6__funcIZ4mainE3$_0NS_9allocatorIS2_EEFvPKcEEclEOS6_.exit 
             ## =>This Inner Loop Header: Depth=1 
# 
# notice that the loop itself uses more instructions than the call?? 
# 

    leaq L_.str1(%rip), %rax 
    movq %rax, -88(%rbp) 
    movq -48(%rbp), %rdi 
    testq %rdi, %rdi 
    je LBB1_1 
## BB#3:        ## %_ZNKSt3__18functionIFvPKcEEclES2_.exit.i.1 
             ## in Loop: Header=BB1_2 Depth=1 
# 
# destructor called once (constant time, therefore irrelevant) 
# 
    movq (%rdi), %rax 
    movq 48(%rax), %rax 
Ltmp5: 
    leaq -88(%rbp), %rsi 
    callq *%rax 
Ltmp6: 
## BB#4:        ## in Loop: Header=BB1_2 Depth=1 
    leaq L_.str2(%rip), %rax 
    movq %rax, -88(%rbp) 
    movq -48(%rbp), %rdi 
    testq %rdi, %rdi 
    jne LBB1_5 
# 
# the rest of this function is exception handling. Executed at most 
# once, in exceptional circumstances. Therefore, irrelevant. 
# 
LBB1_1:         ## in Loop: Header=BB1_2 Depth=1 
    movl $8, %edi 
    callq ___cxa_allocate_exception 
    movq [email protected](%rip), %rcx 
    addq $16, %rcx 
    movq %rcx, (%rax) 
Ltmp10: 
    movq [email protected](%rip), %rsi 
    movq [email protected](%rip), %rdx 
    movq %rax, %rdi 
    callq ___cxa_throw 
Ltmp11: 
    jmp LBB1_2 
LBB1_9:         ## %.loopexit.split-lp 
Ltmp12: 
    jmp LBB1_10 
LBB1_5:         ## %_ZNKSt3__18functionIFvPKcEEclES2_.exit.i.2 
    movq (%rdi), %rax 
    movq 48(%rax), %rax 
Ltmp7: 
    leaq -88(%rbp), %rsi 
    callq *%rax 
Ltmp8: 
## BB#6: 
    movq -48(%rbp), %rdi 
    cmpq %r15, %rdi 
    je LBB1_7 
## BB#15: 
    testq %rdi, %rdi 
    je LBB1_17 
## BB#16: 
    movq (%rdi), %rax 
    callq *40(%rax) 
    jmp LBB1_17 
LBB1_7: 
    movq -80(%rbp), %rax 
    leaq -80(%rbp), %rdi 
    callq *32(%rax) 
LBB1_17:        ## %_ZNSt3__18functionIFvPKcEED1Ev.exit 
    cmpq -32(%rbp), %rbx 
    jne LBB1_19 
## BB#18:        ## %_ZNSt3__18functionIFvPKcEED1Ev.exit 
    xorl %eax, %eax 
    addq $72, %rsp 
    popq %rbx 
    popq %r14 
    popq %r15 
    popq %rbp 
    retq 

Peut-on cesser d'argumenter sur la performance maintenant s'il vous plaît?

+1

L'optimisation des petits foncteurs vous permet d'économiser une allocation, mais elle ne supprime pas tous les coûts de 'std :: function'. –

+1

@ T.C. qui sont quoi, exactement? Un chargement supplémentaire par appel de fonction (qui peut être réorganisé de toute façon)? Si cet appel de fonction va être à l'épicentre des boucles les plus serrées, nous pouvons discuter de l'optimisation. Sinon, c'est une perte de temps totale. Il y aura des fruits beaucoup plus bas ailleurs. –

+1

Il dérange avec inline. –

1

Vous pouvez ajouter une vérification de temps de compilation pour les paramètres de modèle en définissant des contraintes.Cela permettra d'attraper ces erreurs tôt et vous n'aurez pas non plus de temps d'exécution car aucun code n'est généré pour une contrainte utilisant les compilateurs actuels.

Par exemple, nous pouvons définir cette contrainte:

template<class F, class T> struct CanCall 
{ 
    static void constraints(F f, T a) { f(a); } 
    CanCall() { void(*p)(F, T) = constraints; } 
}; 

vérifie CanCall (au moment de la compilation) qu'un F peut être appelée avec T.

Utilisation:

template<typename F> 
void printHello(F f) 
{ 
    CanCall<F, const char*>(); 

    f("Hello!"); 
} 

En Les compilateurs de résultats donnent également des messages d'erreur lisibles pour une contrainte ayant échoué.

+0

Merci, mais cela ne le fait pas échouer sur la ligne qui appelle incorrectement «printHello», mais dans «CanCall». – rustyx

1

Eh bien ... Il suffit d'utiliser SFINAE

template<typename T> 
auto printHello(T f) -> void_t<decltype(f(std::declval<const char*>()))> { 
    f("hello"); 
} 

Et void_t est implémenté comme:

template<typename...> 
using void_t = void; 

Le type de retour va agir comme une contrainte sur le paramètre envoyé à votre fonction. Si l'expression à l'intérieur du decltype ne peut pas être évaluée, cela entraînera une erreur.

+0

Peut se faire sans modèle d'aide: 'auto printHello (T f) -> decltype (f (std :: declval ()), void())'. Les termes avant la virgule sont évalués mais sont ignorés, même dans 'decltype'. – rustyx