如何检查模板参数是否可以使用给定签名

时间:2017-12-07 15:36:18

标签: c++ c++11 templates template-meta-programming callable-object

基本上,我想要实现的是编译时验证(带有可能很好的错误消息),注册可调用(函数,lambda,带调用运算符的结构)具有正确的签名。示例(要填写static_assert的内容):

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

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

  std::function<Signature> callback;
};

7 个答案:

答案 0 :(得分:9)

您可以使用std::is_convertible(自C ++ 11起),例如

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

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

LIVE

答案 1 :(得分:8)

大多数答案都集中在基本上回答这个问题:你可以用这些类型的值调用给定的函数对象吗?这与匹配签名不同,因为它允许您说您不想要的许多隐式转换。为了获得更严格的匹配,我们必须做一堆TMP。首先,这个答案:more显示了如何获取参数的确切类型和返回可调用类型。代码转载于此:

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);
};

完成后,您现在可以在代码中放置一系列静态断言:

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;
};

由于你通过了价值,这基本上就是你所需要的。如果您通过引用传递,我会添加一个额外的静态断言,您可以使用其中一个其他答案;可能是宋元瑶的回答。这样可以处理例如基类型相同但const限定方向错误的情况。

你当然可以在类型Signature上使这一切都是通用的,而不是做我做的事情(简单地重复静态断言中的类型)。这会更好,但它会为已经非常重要的答案添加更复杂的TMP;如果你觉得你会在许多不同的Signature中使用它,或者它经常变化,那么也可能值得添加这些代码。

以下是一个实例:Call function with part of variadic arguments。特别是我的例子:

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);
}

答案 2 :(得分:5)

在C ++ 17中有特质$widthXl: 1000px; $widthSm: 500px; @mixin med ($prop, $xl, $sm) { @media (max-width: $widthXl) { & { #{$prop}: $xl; } } @media (max-width: $widthSm) { & { #{$prop}: $sm } } } body { @include med(color, red, blue) } ,它完全按照你的要求行事。它优于is_invocable<Callable, Args...>的优点是您不必指定返回类型。 这可能听起来有点矫枉过正,但最近我遇到了必须使用它的问题,我的包装函数确实从传递的Callable中推断出它的返回类型,但是我已经传递了模板化的lambda,就像这个is_convertible<std::function<Signature>,...>一样,所以它的返回类型是在suball中推断。我无法将其转换为[](auto& x){return 2*x;},最后我使用std::function的本地实现来实现C ++ 14。我无法找到我从中得到它的链接......无论如何,代码:

is_invocable

并为您的例子:

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;
};

答案 3 :(得分:2)

如果您接受在可变参数模板类中转换A,则可以使用decltype(),仅在Register兼容时激活callable,如下所示

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;
 };

这样,如果您愿意,使用不兼容的函数调用Register(),则可以获得软错误并激活另一个Register()函数

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

答案 4 :(得分:1)

你可以使用检测成语,这是一种形式的sfinae。我相信这适用于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 {};

然后你可以在你的代码中写一个静态断言,如下所示:

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

这比我上面看到的答案的优点是:

  • 适用于任何可调用的,而不仅仅是lambda
  • 没有运行时开销或std::function业务。例如,std::function可能导致动态分配,否则就不必要了。
  • 您实际上可以针对测试编写static_assert并在其中放置一个很好的人类可读错误消息

Tartan Llama撰写了一篇关于这项技术的精彩博文,以及其他几种选择,请查看! https://blog.tartanllama.xyz/detection-idiom/

如果您需要这么做,那么您可能需要查看callable_traits库。

答案 5 :(得分:1)

当您可以使用 C++17 时,这是@R2RT 答案的另一个版本。我们可以使用特征 is_invocable_r 来完成这项工作:

struct Registry {
  std::function<void(int, double)> callback;

  template <typename Callable, 
      std::enable_if_t<
          std::is_invocable_r_v<void, Callable, int, double>>* = nullptr>
  void Register(Callable callable) {
    callback = callable;
  }
};

int main() {
  Registry r;
  r.Register([](int a, double b) { std::cout << a + b << std::endl; });
  r.callback(35, 3.5);
}

打印出来 38.5

std::is_invocable_r 的好处在于它允许您控制返回类型和参数类型,而 std::is_invocable 仅用于可调用的参数类型。

答案 6 :(得分:0)

在这种情况下,您可以使用非常简单的库Boost.Callable Traits

使用示例:

#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;
    }
}

要获取功能类型,可以使用boost::callable_traits::function_type_t<decltype(func)>

mainregister_handler函数中可以看到,可以使用expected_function_type“ function”比较boost::callable_traits::function_type_t<FUNCTION>类型和函数类型(std::is_same_v) -> https://en.cppreference.com/w/cpp/types/is_same

如果您想运行我的示例,请使用例如gcc 7.1.0使用boost 1.66.0和c ++ 17对其进行编译。 Here您可以在线进行:)