2014-09-01 1 views
4

EDIT:Подход, о котором говорится в вопросе, проблематичен по нескольким причинам. В конце концов я решил это, идя по-другому, см. Мой ответ ниже.Проверка типов шаблонов шаблонов

У меня есть несколько классов шаблонов, где ожидается, что параметр шаблона будет соответствовать определенному сигнатуру. Если пользователь поставляет аргумент шаблона, который либо не является вызываемым, либо не соответствует ожидаемой сигнатуре, тогда компиляция выходит из строя внутри механизма обратного вызова, и возникающие в результате сообщения об ошибках очень трудно расшифровать. Вместо этого я хотел бы использовать static_assert, чтобы обеспечить хорошее, понятное сообщение об ошибке вверх, если данный параметр шаблона недействителен. К сожалению, это довольно сложно сделать.

Я использую следующие настройки:

#include <type_traits> 

namespace detail { 

template <typename Function, typename Sig> 
struct check_function 
{ 
    static constexpr bool value = 
     std::is_convertible<Function, typename std::decay<Sig>::type>::value; 
}; 

template <typename, typename> 
struct check_functor; 

template <typename Functor, typename Ret, typename... Args> 
struct check_functor<Functor, Ret(Args...)> 
{ 
    typedef Ret (Functor::*Memfn) (Args...); 

    static constexpr bool value = 
     check_function<decltype(&Functor::operator()), Memfn>::value; 
}; 

} // end namespace detail 

template <typename Func, typename Sig> 
struct check_function_signature 
{ 
    using Type = 
     typename std::conditional< 
        std::is_function<Func>::value, 
        detail::check_function<Func, Sig>, 
        detail::check_functor<Func, Sig>>::type; 

    static constexpr bool value = Type::value; 
}; 

Func т.е. если тип указателя функции, он непосредственно по сравнению с необходимой подписи, а в противном случае предполагается, что функтор и его operator() является вместо этого.

Это, кажется, работает для простых функций и определяемых пользователем функторов, но по некоторым причинам я не могу понять, она не для лямбды (протестировано с Clang 3.4 и г ++ 4.8):

int f(int i); 

struct g 
{ 
    int operator() (int i) { return i; } 
}; 

int main() 
{ 
    static_assert(check_function_signature<decltype(f), int(int)>::value, 
        "Fine"); 

    static_assert(check_function_signature<g, int(int)>::value, 
        "Also fine"); 

    auto l = [](int i) { return i; }; 
    static_assert(check_function_signature<decltype(l), int(int)>::value, 
        "Fails"); 
} 

Мое понимание что стандарт требует, чтобы лямбда были реализованы как анонимные функторы, эквивалентные g выше, поэтому я не могу понять, почему первое работает, но последнее не работает.

Итак, в общем, мои вопросы:

  • ли подход, который я использовал здесь на самом деле разумно, или я сделал очевидную ошибку?
  • Почему это, похоже, работает для определяемых пользователем функторов, но не для компилятора (например, lambdas)?
  • Есть ли исправление/обходное решение, чтобы лямбды можно было проверить таким образом?
  • Есть ли еще какие-либо улучшения, которые я мог бы внести в этот код? (Возможно, много ...)

Заранее благодарим за это, это подталкивает рамки моих знаний метапрограммирования шаблона, поэтому любые советы будут с благодарностью получены.

+1

Код с [этого ответа] (http://stackoverflow.com/a/12283159/3920237), похоже, работает. [Живой пример] (http://coliru.stacked-crooked.com/a/f951bb2b4ca90efd) – 2014-09-01 11:02:41

ответ

2

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

На основании ответов, в частности связанных ответов в комментарии Remaybel, я закончил с большим количеством кода, который лишил тип класса из operator() функтора и проверил каждый тип аргумента против необходимой сигнатуры. Однако это оказалось не слишком хорошим, так как требование получить указатель на T::operator() означало, что он был сбой при наличии нескольких перегрузок operator() или если он был определен как шаблон. Я также не уверен, что он правильно обрабатывал преобразования аргументов во всех случаях, и есть много вещей, которые сложно сделать правильно.

Подумав об этом еще немного, я понял, что я действительно хотел сделать, это попытаться построить вызов функции с моими необходимыми типами аргументов и потерпеть неудачу, если такой вызов не может быть выполнен. Немного взлом позже, и я пришел с этим:

template <typename, typename, typename = void> 
struct check_signature : std::false_type {}; 

template <typename Func, typename Ret, typename... Args> 
struct check_signature<Func, Ret(Args...), 
    typename std::enable_if< 
     std::is_convertible< 
      decltype(std::declval<Func>()(std::declval<Args>()...)), 
      Ret 
     >::value, void>::type> 
    : std::true_type {}; 

Это создает вызов «фиктивную» функцию с использованием declval как для отзывного себя и аргументов, и проверяет, что результат может быть преобразован в требуемый тип. Если такой вызов недействителен, SFINAE запускается, и частичная специализация отклоняется.

Это короче и (ИМО) намного более элегантно, чем то, что я пытался сделать раньше. Он также работает правильно для каждого вызываемого я пытался бросить на него.

Тем не менее, как я уже сказал в первоначальном вопросе, это подталкивает пределы моего знания метапрограммирования, поэтому, если есть какие-либо предложения по улучшению этого кода, пожалуйста, дайте мне знать.

1

Вы пропустите спецификацию const в operator().

С:

template <typename Functor, typename Ret, typename... Args> 
struct check_functor<Functor, Ret(Args...)> 
{ 
    typedef Ret (Functor::*Memfn) (Args...) const; // const added here 

    static constexpr bool value = 
     check_function<decltype(&Functor::operator()), Memfn>::value; 
}; 

Проверкой является правильной для (не изменяемой) лямбды (но не для пользовательского изменяемого функтора). Иначе вы должны сделать вашу лямбду изменяемой:

auto l = [](int i) mutable { return i; };