2015-11-24 9 views
4

Я хочу, чтобы в моем коде была проверка времени компиляции, которая гарантирует, что данный класс перегружает оператор (), что этот оператор принимает в качестве параметров const char * и size_t и что его тип возврата является unsigned integer.Проверка времени компиляции функтора

Я попробовал несколько фрагментов кода, взятых из StackOverflow, но я не доволен решением я написал:

#include <type_traits> 
#include <cstdint> 
#include <iostream> 
#include <memory> 

template<class> 
struct sfinae_true : std::true_type{}; 

namespace detail{ 
    template<class T> 
    static auto test(int) 
    -> sfinae_true<decltype(std::declval<T>()(static_cast<const char *>(nullptr), static_cast<size_t>(0u)))>; 
    template<class> 
    static auto test(long) -> std::false_type; 
} // detail:: 

template<class T> 
struct is_functor : decltype(detail::test<T>(0)){ }; 

template <typename T, typename HashFn, 
     typename std::enable_if<std::is_unsigned<T>::value, int>::type = 0> 
struct Calculation { 
    Calculation() { 
    static_assert(is_functor<HashFn>(), "BAD signature"); 
    typedef typename std::result_of<decltype(&HashFn::operator())(HashFn, const char *, size_t)>::type return_type; 
    static_assert(std::is_unsigned<return_type>::value, "BAD return type"); 
    } 

    T output() { 
    return static_cast<T>(HashFn()(nullptr, 10)); 
    } 
}; 

struct Hash { 
    uint32_t operator()(const char *buffer, size_t n) const { 
    return 65; 
    } 
}; 

int main() { 
    Calculation<uint64_t, Hash> c; 
    c.output(); 
} 

Извините за длиной коды, я пытался сохранить его как можно ,

Вот что мне не нравится мой код:

  1. Если я заменяю int к size_t в списке параметров при перегрузке оператора (), нет ошибок при компиляции, потому что size_t может быть неявно отбрасывается до int.

  2. Если подпись неверна (например, я удаляю const при перегрузке оператора), первое утверждение терпит неудачу. Однако, поскольку компиляция не останавливается, я получаю три сообщения об ошибках, а на выходе компилятора несколько суматоху

    rty.cpp: In instantiation of ‘Calculation<T, HashFn, <anonymous> >::Calculation() [with T = long unsigned int; HashFn = Hash; typename std::enable_if<std::is_unsigned<_Tp>::value, int>::type <anonymous> = 0]’: 
    rty.cpp:41:31: required from here 
    rty.cpp:24:5: error: static assertion failed: BAD signature 
    static_assert(is_functor<HashFn>(), "BAD signature"); 
    ^ 
    rty.cpp:25:104: error: no type named ‘type’ in ‘class std::result_of<unsigned int (Hash::*(Hash, const char*, long unsigned int))(char*, long unsigned int) const>’ 
    typedef typename std::result_of<decltype(&HashFn::operator())(HashFn, const char *, size_t)>::type return_type; 
                                ^
    rty.cpp:26:75: error: no type named ‘type’ in ‘class std::result_of<unsigned int (Hash::*(Hash, const char*, long unsigned int))(char*, long unsigned int) const>’ 
    static_assert(std::is_unsigned<return_type>::value, "BAD return type"); 
    
  3. Я хотел бы иметь один вызов static_assert, что-то вроде:

    static_assert(is_correct_functor<HashFn>(), "BAD implementation"); 
    

Как я могу это достичь? Спасибо за вашу помощь.

Я использую C++ 11 и компиляции с г ++ 4.8

ответ

1

Вы можете использовать этот callable_traits получить тип возвращаемого значения и тип аргументов функтора, и использовать std::is_same сделать утверждение в static_assert

// callable_traits 

namespace detail { 
    template <class ReturnType, class... Args> 
    struct callable_traits_base 
    { 
     using return_type = ReturnType; 
     using argument_type = std::tuple<Args...>; 

     template<std::size_t I> 
     using arg = typename std::tuple_element<I, argument_type>::type; 
    }; 
} 

template <class T> 
struct callable_traits : callable_traits<decltype(&T::operator())> 
{}; 

// lambda/functor 
template <class ClassType, class ReturnType, class... Args> 
struct callable_traits<ReturnType(ClassType::*)(Args...) const> 
: detail::callable_traits_base<ReturnType, Args...> 
{}; 

struct Hash { 
    uint32_t operator()(const char *buffer, size_t n) const { 
    return 65; 
    } 
}; 

static_assert(std::is_same<callable_traits<Hash>::return_type, uint32_t>::value, ""); 
static_assert(std::is_same<callable_traits<Hash>::argument_type, std::tuple<const char *, size_t>>::value, ""); 

Online demo

Вы можете проверить полную реализацию callable_traitshere

+0

Спасибо Брайан, я сделал большой прогресс с вашим ' callable_traits'. Я замечаю, что когда я комментирую перегрузку '()' оператора '' Hash', я получаю много сообщений об ошибках. Есть ли способ улучшить выход компилятора в этом случае? – Antonin

+0

Мне удалось найти решение. Еще раз спасибо Брайан! – Antonin

+0

@ Антонин: можете ли вы поделиться этим решением? – Eric

0

Меня попросили поделиться своим окончательным кодом. Он полагается на ответ Брайана Чена, который я уже принял. В приведенном ниже коде я очень старался иметь чистые сообщения об ошибках от компилятора (в моем случае g ++ 4.8) для всех «возможных» случаев ошибки:

  • HashFn аргумент шаблона не функтор
  • возвращаемый тип перегруженной () не целое без знака (в широком смысле, а не только unsigned int)
  • параметры перегруженного () неверны

Это потому, что этот код будет идти в библиотеку, и я хочу, чтобы сообщения об ошибках компиляции уверены, не слишком неясными.

#include <iostream> 
#include <type_traits> 
#include <functional> 

template <typename T> 
struct defines_functor_operator 
{ 
    typedef char (& yes)[1]; 
    typedef char (& no)[2]; 

    // we need a template here to enable SFINAE 
    template <typename U> 
    static yes deduce(char (*)[sizeof(&U::operator())]); 
    // fallback 
    template <typename> static no deduce(...); 

    static bool constexpr value = sizeof(deduce<T>(0)) == sizeof(yes); 
}; 

// callable_traits 

namespace detail { 
    template <class ReturnType, class... Args> 
    struct callable_traits_base 
    { 
    using return_type = ReturnType; 
    using argument_type = std::tuple<Args...>; 

    template<std::size_t I> 
    using arg = typename std::tuple_element<I, argument_type>::type; 
    }; 
} 

template <class T> 
struct callable_traits : callable_traits<decltype(&T::operator())> 
{}; 

// lambda/functor 
template <class ClassType, class ReturnType, class... Args> 
struct callable_traits<ReturnType(ClassType::*)(Args...) const> 
    : detail::callable_traits_base<ReturnType, Args...> 
{}; 

struct Hash { 
    uint32_t operator()(const char *buffer, size_t n) const { 
    return 65; 
    } 
}; 

template <bool functor, typename H> 
struct HashChecker { 
    static bool constexpr valid_hash = false; 
}; 

template <typename H> 
struct HashChecker<true, H> { 
private: 
    typedef typename callable_traits<H>::return_type return_type; 
    typedef typename callable_traits<H>::argument_type argument_type; 

    static bool constexpr v1 = std::is_unsigned<return_type>::value; 
    static bool constexpr v2 = 
    std::is_same<argument_type, std::tuple<const char *, size_t>>::value; 

    static_assert(v1, "Invalid return type for HashFn"); 
    static_assert(v2, "Invalid parameters for HashFn"); 

protected: 
    static bool constexpr valid_hash = v1 && v2; 

}; 

template <typename T, typename HashFn, 
     typename std::enable_if<std::is_unsigned<T>::value, int>::type = 0> 
struct Calculation 
    : HashChecker<defines_functor_operator<HashFn>::value, HashFn> { 

    typedef HashChecker<defines_functor_operator<HashFn>::value, HashFn> HC; 

    static_assert(defines_functor_operator<HashFn>::value, 
     "HashFn needs to overload '()' operator"); 

    Calculation() { 
    } 

    template <typename U = T> 
    typename std::enable_if<HC::valid_hash, U>::type output() { 
    return static_cast<U>(HashFn()(nullptr, 10)); 
    } 

    template <typename U = T> 
    typename std::enable_if<!HC::valid_hash, U>::type output() { 
    return static_cast<U>(0u); 
    } 

}; 

int main() 
{ 
    Calculation<uint64_t, Hash> c; 
    c.output(); 
    return 0; 
} 
0

Вот еще очень коротка решение:

template <typename T, typename Signature> 
struct is_callable: std::is_convertible<T,std::function<Signature>> { }; 

Вы могли бы использовать, если для вашей конкретной подписи, как это:

struct Hash { 
    uint32_t operator()(const char *buffer, size_t n) const { 
    return 65; 
    } 
}; 

bool is_valid_fcn = is_callable<Hash,uint32_t(const char*, size_t)>::value; 

 Смежные вопросы

  • Нет связанных вопросов^_^