2012-02-10 5 views
11

He rasgo is_callable define así:Cómo escribir el mejor rasgo is_callable posible para el operador con plantilla()

#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 

Mi pregunta es cómo detectar operador de plantilla(), que no tiene argumentos y sólo tiene T tipo de retorno

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

o

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

sé que estas situaciones son raras, pero I wan Pregunto si hay alguna forma de detectar la presencia del operador con plantilla() sin argumentos y con uno o más argumentos de plantilla.

+0

puede ser esto sería muy útil: http://stackoverflow.com/questions/9117603/ how-does-this-has-member-class-template-work/9117836 # 9117836 – Lol4t0

+0

La sobrecarga nunca se basa en el tipo de devolución. –

+0

@MatthieuM. Disculpa, me equivoque. Accidentalmente mezclé dos preguntas diferentes ... :(He editado la pregunta. Quería preguntar si hay alguna forma de detectar la presencia del operador con plantilla() sin argumentos y con uno o más argumentos de plantilla. No puedo simplemente llamar al operador() porque no hay información de cuántos argumentos de plantilla hay y no sé cómo crear una instancia, es decir, no hay forma de deducir los argumentos de la plantilla. –

Respuesta

4

Si sabe de antemano operator() no va a estar sobrecargado, puede intentar tomar su dirección. Si operator() es posiblemente sobrecargado, entonces un resultado positivo significaría que hay un presente operator(), pero un resultado negativo significa que no está presente operator(), o al menos dos sobrecargas.

Observe que una plantilla traerá (como se esperaba) varias sobrecargas de operator(). Sin embargo, si conoce el número de parámetros de plantilla que no están predeterminados, puede intentar tomar la dirección de operator()<T> (para algún tipo T que con suerte no activará SFINAE).

Como nota final, sugiero no tratar de pasar demasiado tiempo tratando de inspeccionar los funtores (o las funciones de los miembros, por las mismas razones) sin saber qué argumentos aprobar, al igual que lo que ya tiene. C++ 11 hace que sea muy fácil escribir y usar código genérico que funcione en el nivel de expresión.

+0

puede crear una clase auxiliar que herede de la clase e implemente su propio operador(). Entonces deberías poder diferenciar el caso ambiguo. –

1

Está intentando detectar una plantilla de función de miembro operator() con parámetros de plantilla no deducidos, que en realidad no es "invocable" y también es inútil, la plantilla de función debería tener un nombre real, porque su ejemplo realmente pierde el sentido de todo el asunto operator. Pero solucionemos tu problema de todos modos.

Permítanme prefacio con un enchufe para una solución de biblioteca en la que estoy trabajando, llamado CallableTraits (una vez más, un trabajo en progreso).

Aunque su caso no es manejado por CallableTraits, la biblioteca emplea una técnica que estoy a punto de describir para resolver un problema muy similar. La técnica es un corte total, pero que es compatible con el estándar, y trabaja para mí en las siguientes plataformas:

  • GCC 5.2 y posterior
  • Clang 3.5 y posterior
  • Visual Studio 2015 Actualización 1 - básicamente funciona

Nota: Visual Studio 2015 Actualización 2 está roto, porque deduce incorrectamente std::index_sequence<I...> en especializaciones parciales ... Archivé un informe de error. Ver here para una descripción.

Nota: Si la implementación de su biblioteca estándar no tiene std::disjunction, puede utilizar la implementación de ejemplo here.

Llamo a la técnica plantilla gusano. Es el equivalente de la metaprogramación de escupir en un pozo profundo y oscuro, solo para escuchar cuánto tiempo lleva salpicar.

¿Qué es un gusano de plantilla?

  1. Una plantilla de gusano es una clase que es convertible a cualquier cosa.
  2. Cualquier expresión de operador con un operador de gusano de plantilla siempre evaluará a otro gusano de plantilla.
  3. Un gusano de plantilla solo se puede usar en un contexto no evaluado. En otras palabras, solo puede usarlo cuando decltype rodea la expresión de nivel superior, al igual que std::declval<T>().

Un gusano de plantilla se menea en lugares en los que no debería estar, y se adhiere al primer tipo de concreto que puede encontrar. De manera similar, un gusano real se pegará al concreto en cualquier tarde de julio.

Para resolver su problema, comenzaremos sin argumentos, luego trabajaremos recursivamente hasta un límite arbitrario de 10. Intentamos realizar la llamada al objeto de función (potencial) tratando de pasar la plantilla por función invocación de estilo, Y por argumento de tipo de plantilla (según sus requisitos).

Este código no tiene en cuenta la semántica de INVOKE, porque eso requiere significativamente más código. Si necesita que esto funcione con las funciones de punteros a miembros y punteros a datos de miembros, puede implementar su propia implementación para eso.

Puede que no haya cubierto todos los operadores, y puede que no los haya implementado todos correctamente, pero verá el punto.

Una última cosa:

Sé de una captura. El tipo de devolución no puede depender de dependent name (que no sean operadores miembros).

Editar: Además, la instanciación de invocación/plantilla debe ser compatible con SFINAE (es decir, no static_assert s).

Sin más preámbulos, aquí es su solución independiente (aunque es posible que desearías no haber pedido):

#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() {} 
Cuestiones relacionadas