2015-11-24 53 views
4

我想在我的代碼中進行編譯時檢查,以確保給定的類超載()運算符,該運算符使用const char *size_t作爲參數,並且其返回類型爲無符號整數。功能函數的編譯時檢查

我試圖從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. 如果我重載()操作時,在參數表替代intsize_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和G ++編譯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

我設法找到解決方案。再次感謝Bryan! – Antonin

+0

@Antonin:你可以分享這個解決方案嗎? – Eric

0

我被要求分享我的最終代碼。這取決於Bryan Chen的答案,我已經接受了。在下面的代碼中,我非常努力地從編譯器中得到乾淨的錯誤消息(在我的例子中是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;