2012-02-10 2 views
11

에 대한 최상의 is_callable 특성을 작성하는 방법 : 인수를 만 가지고하지 않는)나는이 같은 is_callable 특성을 정의한 템플릿 연산자()

#ifndef IS_CALLABLE_HPP 
#define IS_CALLABLE_HPP 

#include <type_traits> 

namespace is_callable_detail 
{ 
    struct no {}; 
    struct yes { no x[2]; }; 

    template<bool CallableArgs, typename Callable, typename ReturnType, typename ...Args> 
    struct check_return 
    { 
     static const bool value = std::is_convertible<decltype(std::declval<Callable>()(std::declval<Args>()...)), ReturnType>::value; 
    }; 

    template<typename Callable, typename ReturnType, typename ...Args> 
    struct check_return<false, Callable, ReturnType, Args...> 
    { 
     static const bool value = false; 
    }; 
} 

template<typename Callable, typename Function> 
struct is_callable; 

template<typename Callable, typename ReturnType, typename ...Args> 
struct is_callable<Callable, ReturnType(Args...)> 
{ 
    private: 
     template<typename T> 
     static is_callable_detail::yes check(decltype(std::declval<T>()(std::declval<Args>()...)) *); 
     template<typename T> 
     static is_callable_detail::no check(...); 

     static const bool value_args = sizeof(check<Callable>(nullptr)) == sizeof(is_callable_detail::yes); 
     static const bool value_return = is_callable_detail::check_return<value_args, Callable, ReturnType, Args...>::value; 
    public: 
     static const bool value = value_args && value_return; 
}; 

#endif // IS_CALLABLE_HPP 

내 질문은 템플릿 연산자를 (검색하는 방법입니다 반환 형식 T

template<typename T> 
T operator()() 
{ 
    // ... 
} 

또는

template<typename T, typename U> 
auto operator()() -> decltype(std::declval<T>() + std::declval<U>()) 
{ 
    // ... 
} 

나는이 상황이 드문 것을 알고,하지만 난 완 인수가없고 하나 이상의 템플릿 인수가있는 템플릿 연산자()의 존재를 감지하는 방법이 있는지 물어보십시오.

+0

는이 유용한 것이 될 수있다 : (당신이 요청하지 않았 할 수도 있지만) 그 이상의 야단 법석없이

가 여기에 독립형 솔루션입니다 http://stackoverflow.com/questions/9117603/ how-does-has-has-has-member-class-template-work/9117836 # 9117836 – Lol4t0

+0

오버로드는 반환 유형을 기반으로하지 않습니다. –

+0

@MatthieuM. 미안 내 실수. 나는 실수로 두 가지 다른 질문을 혼합했다. :(나는 질문을 편집했다. 나는 인수가없고 하나 이상의 템플릿 인자가있는 템플릿 연산자()의 존재를 감지 할 수있는 방법이 있는지 묻고 싶다. 단순히 operator()를 호출 할 수 없다. 얼마나 많은 템플릿 인자가 있는지에 대한 정보가 없기 때문에 그것을 인스턴스화하는 방법을 모른다. 즉, 템플릿 인자를 추론 할 방법이 없다. –

답변

4

미리 알고 싶다면 operator()은 오버로드되지 않으므로 주소를 가져 가려고 할 수 있습니다. operator()인 경우이 과부하 인 경우 긍정적 인 결과는 operator()이 있음을 의미하지만 부정적인 결과는 operator()이 없거나 적어도 두 개의 과부하가 있음을 의미합니다.

템플리트는 operator()의 여러 오버로드를 가져옵니다 (예상대로). 그러나 기본값이 아닌 템플릿 매개 변수의 수를 알고있는 경우 operator()<T>의 주소를 사용해볼 수 있습니다 (T의 경우 은 SFINAE를 트리거하지 않음).

마지막으로, 필자는 이미 가지고있는 것과 같은 인수를 알지 못하고 펑터 (또는 멤버 함수)를 검사하려고 너무 많은 시간을 소비하지 않으려 고 제안합니다. C++ 11은 표현식 수준에서 작동하는 일반 코드를 작성하고 사용하는 것을 매우 쉽게 만듭니다.

+0

클래스에서 상속 받고 자체 operator()를 구현하는 도우미 클래스를 만들 수 있습니다. 그러면 모호한 경우를 구분할 수 있어야합니다. –

1

실제로 "호출 가능하지"않은 템플릿 템플릿 매개 변수가있는 operator() 멤버 함수 템플릿을 감지하려고합니다. 또한 무의미합니다. 함수 템플릿에는 실제 이름이 있어야합니다. 당신의 예는 정말로 operator 것을 가리킨다. 하지만 어쨌든 문제를 해결해 보겠습니다.

제가 작업하고있는 라이브러리 솔루션 용 플러그를 CallableTraits (다시 진행중인 작업)이라고합니다.

사례는 CallableTraits에서 처리하지 않지만이 라이브러리는 비슷한 문제를 해결하기 위해 설명 할 기법을 사용합니다. 이 기술은 총 해킹이지만, 표준 준수, 그리고 다음 플랫폼에서 나를 위해 작동 :

  • GCC 5.2 이후
  • 연타 3.5 이상
  • 의 Visual Studio 2015 업데이트 1 - 기본적으로
  • 를 작동

참고 : Visual Studio 2015 업데이트 2는 부분 전문화 과정에서 std::index_sequence<I...>을 잘못 추론하기 때문에 깨졌습니다. 버그 보고서를 제출했습니다. 설명은 here을 참조하십시오.

참고 : 표준 라이브러리 구현에 std::disjunction이없는 경우 대신 here 샘플 구현을 사용할 수 있습니다.

나는이 기술을 템플릿 웜이라고 부릅니다. 깊고 어두운 우물에 침을 뱉어내는 것과 같은 메타 프로그래밍 방식입니다.

템플릿 웜이란 무엇입니까?

  1. 템플릿 웜은 모든 것과 모든 것으로 변환 가능한 클래스입니다.
  2. 템플릿 웜 피연산자가있는 연산자 식은 항상 다른 템플릿 웜으로 평가됩니다.
  3. 템플릿 웜은 평가되지 않은 컨텍스트에서만 사용할 수 있습니다. 다시 말해 decltype이 최상위 표현식을 둘러싸는 경우에만 std::declval<T>()처럼 사용할 수 있습니다.

템플릿 웜은 그것이 존재하지 않을 것으로 예상되는 곳으로 스스로 움직이고 찾을 수있는 첫 번째 구체적인 유형에 집착합니다. 비슷한 방식으로 실제 웜은 7 월 어느 오후에 콘크리트에 붙어 있습니다.

문제를 해결하기 위해 인수없이 시작한 다음 재귀 적으로 최대 10 개까지 작업합니다. 함수를 기준으로 템플릿 웜을 전달하려고 시도하여 (잠재적 인) 함수 객체를 호출하려고합니다. 스타일 호출, AND (템플릿 요구 사항 별).

이 코드는 의미가 더 많은 코드를 필요로하기 때문에 INVOKE 의미를 설명하지 않습니다. 멤버 함수에 대한 포인터와 멤버 함수에 대한 포인터로 작업해야 할 필요가있는 경우이를위한 자체 구현을 롤백 할 수 있습니다.

나는 모든 연산자를 다루지 않았을 수 있으며 올바르게 구현하지 않았지만 그 점을 확인할 수 있습니다.

마지막으로 한 가지 :

나는 하나의 캐치를 알고 있습니다. 반환 유형은 dependent name (멤버 연산자 제외)에 의존 할 수 없습니다.

편집 : 또한 호출/템플릿 인스턴스화는 SFINA에게 친숙해야합니다 (즉, static_assert).

#include <utility> 
#include <type_traits> 

namespace detail { 

    //template_worm CANNOT be used in evaluated contexts 
    struct template_worm { 

     template<typename T> 
     operator T&() const; 

     template<typename T> 
     operator T &&() const; 

     template_worm() = default; 

#ifndef _MSC_VER 

     // MSVC doesn't like this... because it can deduce void? 
     // Whatever, we can do without it on Windows 
     template<typename... T> 
     template_worm(T&&...); 

#endif //_MSC_VER 

     template_worm operator+() const; 
     template_worm operator-() const; 
     template_worm operator*() const; 
     template_worm operator&() const; 
     template_worm operator!() const; 
     template_worm operator~() const; 
     template_worm operator()(...) const; 
    }; 

#define TEMPLATE_WORM_BINARY_OPERATOR(...)         \ 
                      \ 
    template<typename T>             \ 
    constexpr inline auto             \ 
    __VA_ARGS__ (template_worm, T&&) -> template_worm {     \ 
     return template_worm{};           \ 
    }                  \ 
                      \ 
    template<typename T>             \ 
    constexpr inline auto             \ 
    __VA_ARGS__ (T&&, template_worm) -> template_worm {     \ 
     return template_worm{};           \ 
    }                  \ 
                      \ 
    constexpr inline auto             \ 
    __VA_ARGS__ (template_worm, template_worm) -> template_worm {   \ 
     return template_worm{};           \ 
    }                  \ 
    /**/ 

    TEMPLATE_WORM_BINARY_OPERATOR(operator+) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator-) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator/) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator*) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator==) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator!=) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator&&) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator||) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator|) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator&) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator%) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator,) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator<<) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator>>) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator<) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator>) 

    template<std::size_t Ignored> 
    using worm_arg = template_worm const &; 

    template<typename T> 
    struct success {}; 

    struct substitution_failure {}; 

    template<typename F, typename... Args> 
    struct invoke_test { 

     template<typename T, typename... Rgs> 
     auto operator()(T&& t, Rgs&&... rgs) const -> 
      success<decltype(std::declval<T&&>()(std::forward<Rgs>(rgs)...))>; 

     auto operator()(...) const->substitution_failure; 

     static constexpr int arg_count = sizeof...(Args); 
    }; 

    // force_template_test doesn't exist in my library 
    // solution - it exists to please OP 
    template<typename... Args> 
    struct force_template_test { 

     template<typename T> 
     auto operator()(T&& t) const -> 
      success<decltype(std::declval<T&&>().template operator()<Args...>())>; 

     auto operator()(...) const->substitution_failure; 
    }; 

    template<typename T, typename... Args> 
    struct try_invoke { 

     using test_1 = invoke_test<T, Args...>; 

     using invoke_result = decltype(test_1{}(
      ::std::declval<T>(), 
      ::std::declval<Args>()... 
      )); 

     using test_2 = force_template_test<Args...>; 

     using force_template_result = decltype(test_2{}(std::declval<T>())); 

     static constexpr bool value = 
      !std::is_same<invoke_result, substitution_failure>::value 
      || !std::is_same<force_template_result, substitution_failure>::value; 

     static constexpr int arg_count = test_1::arg_count; 
    }; 

    template<typename T> 
    struct try_invoke<T, void> { 
     using test = invoke_test<T>; 
     using result = decltype(test{}(::std::declval<T>())); 
     static constexpr bool value = !std::is_same<result, substitution_failure>::value; 
     static constexpr int arg_count = test::arg_count; 
    }; 

    template<typename U, std::size_t Max, typename = int> 
    struct min_args; 

    struct sentinel {}; 

    template<typename U, std::size_t Max> 
    struct min_args<U, Max, sentinel> { 
     static constexpr bool value = true; 
     static constexpr int arg_count = -1; 
    }; 

    template<typename U, std::size_t Max, std::size_t... I> 
    struct min_args<U, Max, std::index_sequence<I...>> { 

     using next = typename std::conditional< 
      sizeof...(I)+1 <= Max, 
      std::make_index_sequence<sizeof...(I)+1>, 
      sentinel 
     >::type; 

     using result_type = std::disjunction< 
      try_invoke<U, worm_arg<I>...>, 
      min_args<U, Max, next> 
     >; 

     static constexpr bool value = result_type::value; 
     static constexpr int arg_count = result_type::arg_count; 
    }; 

    template<typename U, std::size_t Max> 
    struct min_args<U, Max, void> { 

     using result_type = std::disjunction< 
      try_invoke<U, void>, 
      min_args<U, Max, std::make_index_sequence<1>> 
     >; 

     static constexpr int arg_count = result_type::arg_count; 
     static constexpr bool value = result_type::value; 
    }; 

    template<typename T, std::size_t SearchLimit> 
    using min_arity = std::integral_constant<int, 
     min_args<T, SearchLimit, void>::arg_count>; 
} 

// Here you go. 
template<typename T> 
using is_callable = std::integral_constant<bool, 
    detail::min_arity<T, 10>::value >= 0>; 

// This matches OP's first example. 
struct Test1 { 

    template<typename T> 
    T operator()() { 
     return{}; 
    } 
}; 

// Yup, it's "callable", at least by OP's definition... 
static_assert(is_callable<Test1>::value, ""); 

// This matches OP's second example. 
struct Test2 { 

    template<typename T, typename U> 
    auto operator()() -> decltype(std::declval<T>() + std::declval<U>()) { 
     return{}; 
    } 
}; 

// Yup, it's "callable", at least by OP's definition... 
static_assert(is_callable<Test2>::value, ""); 

// ints aren't callable, of course 
static_assert(!is_callable<int>::value, ""); 

int main() {} 
관련 문제