2013-04-12 1 views
3

У меня есть следующие функции шаблона:Как можно вывести аргумент шаблона после вариационных аргументов?

template <typename...Args, typename Func> 
void call(const char *name, Args...args, Func f) 
{ 
     f(3); 
} 

Когда я пытаюсь использовать его, как

call("test", 1, 2, 3, [=](int i) { std::cout<< i; }); 

компилятор жалуется, что не может вывести аргумент шаблона Func. Как решить эту проблему, зная, что args может быть любым типом, кроме указателя функции.

+1

Можете ли вы REORDER аргументов? – zch

+0

Аргумент name - это метод, а аргументы - аргументы метода, функция func - это функция обратного вызова. Таким образом, порядок кажется «естественным» таким образом. Поэтому я не хочу изменять порядок аргументов. –

+1

В противном случае, правилом языка является то, что пакет параметров должен быть последним аргументом. –

ответ

4

Напишите get_last, который извлекает последний элемент пакета параметров.

Назовите его f. Позвоните по телефону f.

В качестве примера,

template<typename T0> 
auto get_last(T0&& t0)->decltype(std::forward<T0>(t0)) 
{ 
    return std::forward<T0>(t0); 
} 
template<typename T0, typename... Ts> 
auto get_last(T0&& t0, Ts&&... ts)->decltype(get_last(std::forward<Ts>(ts)...)) 
{ 
    return get_last(std::forward<Ts>(ts)...); 
} 

, если вы не заботитесь о разрешении перегрузки, просто звоню get_last и рассматривая его как функтора может быть достаточно:

template <typename...Args> 
void call(const char *name, Args...&& args) 
{ 
    auto&& f = get_last(std::forward<Args>(args)...); 
    f(3); 
} 

Следующим шагом будет чтобы сделать магию SFINAE enable_if, чтобы сделать call не в силе, если вы не пройдете действительный функтор последним: однако это, вероятно, слишком велико.

Чтобы обнаружить, если f(3) будет работать, простой черты класса:

// trivial traits class: 
template<typename T> 
struct is_type:std::true_type {}; 

template<typename Functor, typename=void> 
struct can_be_called_with_3:std::false_type {} 

template<typename Functor> 
struct can_be_called_with_3<Functor, 
    typename std::enable_if< 
    std::is_type< decltype(
     std::declval<Functor>(3) 
    ) >::value 
    >::type 
>:std::true_type {} 

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

Тогда вы увеличить call с:

template <typename...Args> 
auto call(const char *name, Args...&& args) 
    -> typename std::enable_if< 
     can_be_called_with_3< decltype(get_last(std::forward<Args>(args)...)) >::value 
    >::type 
{ /* body unchanged */ } 

который довольно тупые.

+0

Можете ли вы подробно остановиться на этом примере? Учитывая тот факт, что я заранее не знаю тип параметров. –

+0

Нужны ли вам классы 'last_type' или' type_is_functor_that_takes_these_arguments'? (Т. Е. Вас не волнует, если отказ в функторе дает «недействительная функция найдена» или ошибка экземпляра шаблона?) – Yakk

+0

Не вызовет ли 'f (3)' генерировать ошибку, если это не функтор ? –

8

От 14.1p11:

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

Если вы хотите сохранить вызываемым в качестве последнего аргумента, вы можете использовать forward_as_tuple:

template <typename...Args, typename Func> 
void call(const char *name, std::tuple<Args...> args, Func f) 
{ 
     f(3); 
} 

call("test", std::forward_as_tuple(1, 2, 3), [=](int i) { std::cout<< i; }); 

Мы можем на самом деле лучше, синтезируя tuple, чтобы содержать отозваны, а также:

#include <tuple> 

template<typename... Args_F> 
void call_impl(const char *name, std::tuple<Args_F... &&> args_f) { 
    auto &&f = std::get<sizeof...(Args_F) - 1>(args_f); 
    f(3); 
} 

template<typename...ArgsF> 
void call(const char *name, ArgsF &&...args_f) { 
    call_impl(name, std::tuple<ArgsF &&...>(std::forward<ArgsF>(args_f)...)); 
} 
+0

Это также отличное решение. Я определенно хочу скрыть std :: tuple извне, поэтому синтезированное решение приятно. +1! –

0

Если вы хотите использовать пакет аргументов в качестве шаблона, вы не можете написать это так, как вы уже сделали:

template <typename...Args, typename Func> 
void call(const char *name, Args...args, Func f) 
{ 
     f(3); 
} 

Но вы можете упаковать их в std::tuple:

template <typename...Args, typename Func> 
void call(const char *name, std::tuple<Args...> args, Func f) 
{ 
     f(3); 
} 

call("test", std::forward_as_tuple(1, 2, 3), [=](int i) { std::cout<< i; });