c++ - 허용 - 템플릿 hpp




템플릿 인자가 주어진 서명으로 호출 가능한 것인지 확인하는 방법 (4)

C ++ 17에서는 is_invocable<Callable, Args...> 특성이 is_invocable<Callable, Args...> , 이것은 당신이 요구하는 것을 정확하게 수행합니다. is_convertible<std::function<Signature>,...> 비해 장점은 리턴 유형을 지정할 필요가 없다는 것입니다. 그것은 잔인한 소리처럼 들릴지 모르지만 최근에 나는 그것을 사용해야만하는 문제가있었습니다. 정확히 래퍼 함수는 Callable에서 반환 된 타입을 추론했습니다. 그러나 이것과 같은 템플릿 람다를 통과 시켰습니다 [](auto& x){return 2*x;} , 그래서 그것의 반환 유형은 subcall에서 추론되었습니다. 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;
};

귀하의 예를 들면 :

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

기본적으로, 내가 뭘 달성하고 싶습니다 등록 된 호출 가능 (함수, 람다, 호출 연산자와 구조체) 올바른 서명을 가지고 (아마도 좋은 오류 메시지와 함께) 컴파일 타임 검증입니다. 예제 ( static_assert 내용이 채워짐) :

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

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

  std::function<Signature> callback;
};

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!");

위의 응답에 대한 이점은 다음과 같습니다.

  • 그것은 호출 가능한 모든 람다뿐만 아니라 작동합니다.
  • 런타임 오버 헤드 또는 std::function 비즈니스가 없습니다. std::function 은 동적 할당을 일으킬 수 있습니다 (예 : 불필요한 경우).
  • 실제로 테스트에 대해 static_assert 를 작성하고 거기에 인간이 읽을 수있는 멋진 오류 메시지를 넣을 수 있습니다.

타탄 라마 (Tartan Llama)는이 기술에 대한 훌륭한 블로그 포스트를 작성했으며 몇 가지 대안을 살펴보십시오! https://blog.tartanllama.xyz/detection-idiom/

이 작업을 많이해야한다면 callable_traits 라이브러리를 살펴 보는 것이 좋습니다.


대부분의 대답은 기본적으로 질문에 답하는 것에 중점을 둡니다. 주어진 함수 객체를 이러한 유형의 값으로 호출 할 수 있습니까? 이것은 서명을 매칭하는 것과 같지 않습니다. 원하지 않는 많은 암시 적 변환을 허용하기 때문입니다. 더 엄격한 일치를 얻으려면 TMP를 많이해야합니다. 먼저,이 대답은 : 가변 인수의 일부가있는 호출 함수는 호출 가능한 인수 의 정확한 유형과 리턴 유형을 얻는 방법을 보여줍니다. 코드를 여기에 재현 :

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

당신이 값으로 지나가고 있기 때문에 이것은 기본적으로 필요한 것입니다. 참조로 전달하는 경우 다른 대답 중 하나를 사용하는 곳에 정적 어설 션을 추가합니다. 아마 songyuanyao의 대답. 예를 들어 기본 유형이 동일하지만 const 자격이 잘못된 방향으로 진행되는 경우를 처리합니다.

당신은 물론 내가하는 일을하는 대신 (단순히 정적 어설 션에서 타입을 반복하는 것) Signature 타입에 대해이 모든 것을 제네릭으로 만들 수 있습니다. 이것은 더 좋을 것이나 이미 복잡한 응답에 훨씬 더 복잡한 TMP를 추가했을 것입니다. 많은 다른 Signature 와 함께 이것을 사용하거나 자주 바뀌는 것처럼 느껴진다면 그 코드를 추가 할 가치가 있습니다.

다음은 실제 예입니다. http://coliru.stacked-crooked.com/a/cee084dce9e8dc09 . 특히, 나의 예 :

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

이 경우 아주 간단한 라이브러리 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)> 사용할 수 있습니다.

main 함수와 register_handler 함수에서 볼 수 있듯이, std::is_same_v "function"-> https://en.cppreference.com/w/cpp/types/is_same 사용하여 expected_function_type 유형을 함수 유형 ( boost::callable_traits::function_type_t<FUNCTION> )과 비교할 수 https://en.cppreference.com/w/cpp/types/is_same

예제를 실행하려면 gcc 7.1.0을 사용하여 boost 1.66.0 및 c ++ 17로 컴파일하십시오. Here 온라인으로 할 수 있습니다 :)





callable-object