it-swarm.com.de

So prüfen Sie, ob das Vorlagenargument mit einer bestimmten Signatur aufrufbar ist

Grundsätzlich möchte ich die Überprüfung der Kompilierzeit (mit möglicherweise Nice-Fehlermeldung), die registriert aufrufbar ist (entweder eine Funktion, ein Lambda, eine Struktur mit Anrufoperator), die die richtige Signatur hat. Beispiel (Inhalt des static_assert soll gefüllt werden):

struct A {
  using Signature = void(int, double);

  template <typename Callable>
  void Register(Callable &&callable) {
    static_assert(/* ... */);
    callback = callable;
  }

  std::function<Signature> callback;
};
17
pzelasko

Die meisten Antworten konzentrieren sich hauptsächlich auf die Beantwortung der Frage: Können Sie das gegebene Funktionsobjekt mit den Werten dieser Typen call _. Dies stimmt nicht mit dem Abgleichen der Signatur überein, da viele implizite Konvertierungen möglich sind, von denen Sie behaupten, dass Sie sie nicht möchten. Um eine engere Übereinstimmung zu erhalten, müssen wir eine Reihe von TMPs erstellen. Zuerst diese Antwort: Aufruffunktion mit einem Teil der variadischen Argumente zeigt, wie die genauen Typen der Argumente und der Rückgabetyp eines aufrufbaren Objekts abgerufen werden. Code hier reproduziert:

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
    using result_type = ReturnType;
    using arg_Tuple = std::Tuple<Args...>;
    static constexpr auto arity = sizeof...(Args);
};

template <typename R, typename ... Args>
struct function_traits<R(&)(Args...)>
{
    using result_type = R;
    using arg_Tuple = std::Tuple<Args...>;
    static constexpr auto arity = sizeof...(Args);
};

Nachdem Sie das getan haben, können Sie jetzt eine Reihe statischer Asserts in Ihren Code einfügen:

struct A {
  using Signature = void(int, double);

  template <typename Callable>
  void Register(Callable &&callable) {
    using ft = function_traits<Callable>;
    static_assert(std::is_same<int,
        std::decay_t<std::Tuple_element_t<0, typename ft::arg_Tuple>>>::value, "");
    static_assert(std::is_same<double,
        std::decay_t<std::Tuple_element_t<1, typename ft::arg_Tuple>>>::value, "");
    static_assert(std::is_same<void,
        std::decay_t<typename ft::result_type>>::value, "");

    callback = callable;
  }

  std::function<Signature> callback;
};

Da Sie Wert übergehen, ist dies im Grunde alles, was Sie brauchen. Wenn Sie als Referenz übergeben werden, würde ich eine zusätzliche statische Bestätigung hinzufügen, in der Sie eine der anderen Antworten verwenden. wahrscheinlich die Antwort von songyuanyao. Dies würde sich um Fälle kümmern, in denen beispielsweise der Basistyp derselbe war, die const-Qualifizierung jedoch in die falsche Richtung ging.

Sie können dies natürlich über den Typ Signature hinaus generisch machen, anstatt das zu tun, was ich mache (einfach die Typen in der statischen Assertierung wiederholen). Das wäre schöner, aber es hätte einer bereits nicht trivialen Antwort noch komplexeres TMP hinzugefügt. Wenn Sie das Gefühl haben, dass Sie dies mit vielen verschiedenen Signatures verwenden möchten oder dass es sich häufig ändert, lohnt es sich wahrscheinlich, auch diesen Code hinzuzufügen.

Hier ein Live-Beispiel: http://coliru.stacked-crooked.com/a/cee084dce9e8dc09 . Insbesondere mein Beispiel:

void foo(int, double) {}
void foo2(double, double) {}

int main()
{
    A a;
    // compiles
    a.Register([] (int, double) {});
    // doesn't
    //a.Register([] (int, double) { return true; });
    // works
    a.Register(foo);
    // doesn't
    //a.Register(foo2);
}
8
Nir Friedman

Sie können std :: is_convertible (seit C++ 11) verwenden, z.

static_assert(std::is_convertible_v<Callable&&, std::function<Signature>>, "Wrong Signature!");

oder

static_assert(std::is_convertible_v<decltype(callable), decltype(callback)>, "Wrong Signature!");

LEBEN

9
songyuanyao

In C++ 17 gibt es das Merkmal is_invocable<Callable, Args...>, das genau das tut, wonach Sie fragen. Der Vorteil gegenüber is_convertible<std::function<Signature>,...> ist, dass Sie keinen Rückgabetyp angeben müssen. Es klingt vielleicht nach übertrieben, aber in letzter Zeit hatte ich ein Problem, das ich verwenden musste, genau meine Wrapper-Funktion leitete seinen Rückgabetyp von Callable ab, aber ich habe Lambda wie diesen [](auto& x){return 2*x;} übergeben in Unterruf. Ich konnte es nicht in std::function konvertieren und endete mit der lokalen Implementierung von is_invocable für C++ 14. Ich kann den Link nicht finden, woher ich ihn habe ... Jedenfalls der Code:

template <class F, class... Args>
struct is_invocable
{
    template <class U>
    static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
    template <class U>
    static auto test(...) -> decltype(std::false_type());

    static constexpr bool value = decltype(test<F>(0))::value;
};

und für dein Beispiel:

struct A {
using Signature = void(int, double);

template <typename Callable>
void Register(Callable &&callable) {
    static_assert(is_invocable<Callable,int,double>::value, "not foo(int,double)");
    callback = callable;
}

std::function<Signature> callback;
};
4
R2RT

Wenn Sie die Umwandlung von A in eine variadische Vorlagenklasse akzeptieren, können Sie decltype() verwenden, um Register nur zu aktivieren, wenn callable wie folgt kompatibel ist

template <typename R, typename ... Args>
struct A
 {
   using Signature = R(Args...);

   template <typename Callable>
   auto Register (Callable && callable)
      -> decltype( callable(std::declval<Args>()...), void() )
    { callback = callable; }

   std::function<Signature> callback;
 };

Wenn Sie es vorziehen, Register() mit einer inkompatiblen Funktion aufzurufen, können Sie auf diese Weise einen weichen Fehler erhalten und eine andere Register()-Funktion aktivieren

void Register (...)
 { /* do something else */ };
2
max66

Sie können das Erkennungs-Idiom verwenden, eine Form von Sfinae. Ich glaube, das funktioniert in C++ 11.

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

template <typename Callable, typename enable=void>
struct callable_the_way_i_want : std::false_type {};

template <typename Callable>
struct callable_the_way_i_want <Callable, void_t <decltype (std::declval <Callable>()(int {},double {}))>> : std::true_type {};

Dann können Sie wie folgt eine statische Assertierung in Ihren Code schreiben:

static_assert (is_callable_the_way_i_want <Callable>::value, "Not callable with required signature!");

Der Vorteil gegenüber den Antworten, die ich oben sehe, ist:

  • Es funktioniert für jedes anrufbare, nicht nur für ein Lambda
  • es gibt keinen Laufzeit-Overhead oder std::function-Geschäft. std::function kann beispielsweise eine dynamische Zuordnung verursachen, die ansonsten nicht erforderlich wäre.
  • sie können tatsächlich einen static_assert gegen den Test schreiben und dort eine von Nice lesbare Fehlermeldung einfügen

Tartan Llama hat einen großartigen Blogpost über diese Technik geschrieben, und mehrere Alternativen, schau es dir an! https://blog.tartanllama.xyz/detection-idiom/

Wenn Sie dies tun müssen, sollten Sie sich die Bibliothek callable_traits ansehen.

1
Chris Beck

In diesem Fall können Sie eine sehr einfache Bibliothek Boost.Callable Traits verwenden.

Anwendungsbeispiel:

#include <boost/callable_traits.hpp>
#include <iostream>
#include <Tuple>

template<typename F>
void register_handler(F&)
{
    if constexpr (std::is_same_v<boost::callable_traits::function_type_t<F>, void(int&, double)>)
    {
        std::cout << "Register handler with signature void(int&, double)" << std::endl;
    }
    else if constexpr (std::is_same_v<boost::callable_traits::function_type_t<F>, void(int)>)
    {
        std::cout << "Register handler with signature void(int)" << std::endl;
    }
}

void func(int&, double)
{}

auto lambda = [](int) {};

int main()
{
    {
        register_handler(func);
        register_handler(lambda);
    }

    {
        using function_type = boost::callable_traits::function_type_t<decltype(func)>;
        using expected_function_type = void(int&, double);

        std::cout << std::boolalpha << std::is_same_v<expected_function_type, function_type> << std::endl;
    }
}

Um die Art der Funktion zu erhalten, können Sie boost::callable_traits::function_type_t<decltype(func)> verwenden.

Wie Sie in main und register_handler Funktionen sehen können, ist es möglich, expected_function_type type mit dem Funktionstyp (boost::callable_traits::function_type_t<FUNCTION>) zu vergleichen, indem Sie std::is_same_v "function" -> https://en.cppreference.com/w/cpp/types/is_same

Wenn Sie mein Beispiel ausführen möchten, kompilieren Sie es bitte mit Boost 1.66.0 und C++ 17, beispielsweise mit gcc 7.1.0. Hier kannst du es online machen :)

0
MateuszGierczak