2016-11-10 1 views
3

Ich habe eine Klasse Foo, die verschiedene Prädikatvarianten über seinen Konstruktor akzeptiert.Übergabe verschiedener Lambdas zu Funktionsvorlage in C++

template<typename T> 
struct Value 
{ 
    T value; 
}; 

class Foo 
{ 
public: 
    template<typename T> 
    Foo(Value<T> &value, function<bool()> predicate) 
    { 
    } 

    template<typename T> 
    Foo(Value<T> &value, function<bool(const Value<T> &)> predicate) : 
     Foo(value, function<bool()>([&value, predicate](){ return predicate(value); })) 
    { 
    } 
}; 

Dies ermöglicht es mir, die Klasse mit explizitem function Objekt zu konstruieren:

Value<int> i; 
Foo foo0(i, function<bool()>([]() { return true; })); 
Foo foo1(i, function<bool(const Value<int> &)>([](const auto &) { return true; })); 

jedoch scheitert es, wenn sie direkt ein Lambda zu verwenden versuchen:

Foo fooL1(i, [](const Value<int> &) { return true; }); 

Aus einem Grunde, I don‘ Ich verstehe, aber der Compiler berücksichtigt nicht die Verfügbarkeit der impliziten Konvertierung von Lambda zu function in Konstruktor Vorlage. Die Fehlermeldung ist (Visual C++ 2015, Update 3):

Fehler C2664: 'Foo :: Foo (Foo & &)': Konvertierung Argument 2 von 'main :: < lambda_f1d2143f356d549800fb3412d8bc61a2>' zu 'std :: function < bool (void)>'

Jetzt kann ich anderen Konstruktor Vorlage für lambdas hinzufügen

template<typename T, typename UnaryPredicate> 
Foo(Value<T> &value, UnaryPredicate predicate) : 
    Foo(value, function<bool(const Value<T> &)>(predicate)) 
{ 
} 

, die in Ordnung, solange das Lambda zu diesem Konstruktor übergibt funktioniert hat einen Parameter von Value<T>, aber es ist nicht natürlich für lambdas ohne Parameter:

Foo fooL0(i, []() { return true; }); 

Also ich würde wahrscheinlich einige SFINAE Magie benötigen entsprechende Konstruktor Vorlage zu ermöglichen, für verschiedene lambdas, so etwas wie:

template<typename T, typename UnaryPredicate, 
    typename = enable_if_t<is_callable_without_args> > 
Foo(Value<T> &value, UnaryPredicate predicate) : 
    Foo(value, function<bool()>(predicate)) 
{ 
} 

template<typename T, typename UnaryPredicate, 
    typename = enable_if_t<is_callable_with_one_arg> > 
Foo(Value<T> &value, UnaryPredicate predicate) : 
    Foo(value, function<bool(const Value<T> &)>(predicate)) 
{ 
} 

Oder vielleicht nur ein Konstruktor Vorlage könnte den Trick tun, so etwas wie:

template<typename T, typename UnaryPredicate> 
Foo(Value<T> &value, UnaryPredicate predicate) : 
    Foo(value, function<???decltype(UnaryPredicate)???>(predicate)) 
{ 
} 

Oder vielleicht eine ganz andere Lösung? Die Frage ist, wie Konstruktorüberladungen mit entsprechenden Lambdas arbeiten können.

Antwort

4

Ihr Problem ist, dass C++ alle Argumente gleich behandelt und versucht, Ihre Vorlage Argumente von allen abzuleiten.

Fehler beim Ableiten der verwendeten Template-Argumente ist ein Fehler, nicht nur eine inkonsistente Ableitung. Es braucht einfach nicht diejenigen, die passen und "damit" gehen.

Wir können ein Template-Argument als nicht abgeleitet markieren:

template<class T> struct tag_t {using type=T;}; 
template<class Tag> using type=typename Tag::type; 

template<class T> 
using block_deduction = type<tag_t<T>>; 

dann:

template<class T> 
Foo(
    Value<T> &value, 
    block_deduction<function<bool(const Value<T> &)>> predicate 
) : 
    Foo(
    value, 
    [&value, predicate=std::move(predicate)]{ return predicate(value); } 
) 
{} 

jetzt T nur aus dem ersten Argument abgeleitet wird. Normale Umwandlung tritt für 2.

(Kleinere Formatierungsänderungen/Optimierungen/Kürzung des Codes gilt für Ihre Foo über block_deduction hinaus auch enthalten.)

+0

Großartig, obwohl ich nicht hinter der Magie sehe, funktioniert es wie ein Zauber. Können Sie mir irgendwann auf einige Informationen hinweisen, die diese Technik in lesbarer Form beschreiben? Vielen Dank. – manison

+1

@manisin Block deduceton nimmt einen Typ und gibt einen gleichen Typ zurück. Der zurückgegebene Typ ist jedoch nicht mehr ableitbar. Die Vorlaßtypableitung ist ein Mustervergleich, und für 'tag_t :: type' kann kein Musterabgleich durchgeführt werden. Kurz gesagt, weil der Standard das sagt. Warum sagt es so? In der Theorie könnte ":: type" nicht mit "T" in Beziehung stehen, in Wirklichkeit ist es "T". C++ benötigt das Typabzugssystem nicht, um beliebige Turing-vollständige Prozesse zu invertieren, und "tag_t :: type" könnte eine beliebige Zuordnung von "T" zu ":: type" haben. – Yakk