У меня есть функция, которая принимает один параметр со значением по умолчанию. Теперь я также хочу, чтобы он принимал переменное число параметров и направлял их в какую-то другую функцию. Параметры функции со значением по умолчанию должны быть последними, поэтому … могу ли я поместить этот параметр после пакета переменных, и компилятор определит, предоставляю ли я его или нет при вызове функции?
(Предполагая, что пакет не содержит тип этого последнего параметра. При необходимости, мы можем предположить, что, поскольку этот тип обычно не должен быть известен пользователю, в противном случае он все равно считается неправильным использованием моего интерфейса.) ..)
template <class... Args>
void func (Args&&... args, SomeSpecialType num = fromNum(5))
{
}
Нет, пачки должны быть последними.
Но вы можете подделать это. Вы можете определить, что является последним типом в пакете. Если это SomeSpecialType
Вы можете запустить свой функционал. Если это не так SomeSpecialType
, вы можете рекурсивно вызывать себя с вашими аргументами и fromNum(5)
прилагается.
Если вы хотите проявить фантазию, эту проверку можно выполнить во время компиляции (т. Е. С другой перегрузкой) с использованием методов SFINAE. Но это, вероятно, не стоит хлопот, учитывая, что проверка во время выполнения будет постоянной при данной перегрузке и, следовательно, почти наверняка будет оптимизирована, и SFINAE не следует использовать легкомысленно.
Это не дает вам подпись, которую вы хотите, но это дает вам поведение, которое вы хотите. Вы должны будете объяснить предполагаемую подпись в комментариях.
Как то так, после удаления опечаток и тому подобного:
// extract the last type in a pack. The last type in a pack with no elements is
// not a type:
template<typename... Ts>
struct last_type {};
template<typename T0>
struct last_type<T0> {
typedef T0 type;
};
template<typename T0, typename T1, typename... Ts>
struct last_type<T0, T1, Ts...>:last_type<T1, Ts...> {};
// using aliases, because typename spam sucks:
template<typename Ts...>
using LastType = typename last_type<Ts...>::type;
template<bool b, typename T=void>
using EnableIf = typename std::enable_if<b, T>::type;
template<typename T>
using Decay = typename std::decay<T>::type;
// the case where the last argument is SomeSpecialType:
template<
typename... Args,
typename=EnableIf<
std::is_same<
Decay<LastType<Args...>>,
SomeSpecialType
>::value
>
void func( Args&&... args ) {
// code
}
// the case where there is no SomeSpecialType last:
template<
typename... Args,
typename=EnableIf<
!std::is_same<
typename std::decay<LastType<Args...>>::type,
SomeSpecialType
>::value
>
void func( Args&&... args ) {
func( std::forward<Args>(args)..., std::move(static_cast<SomeSpecialType>(fromNum(5))) );
}
// the 0-arg case, because both of the above require that there be an actual
// last type:
void func() {
func( std::move(static_cast<SomeSpecialType>(fromNum(5))) );
}
или что-то в этом роде.
Другой подход состоит в том, чтобы передать аргументы с переменным числом через кортеж.
template <class... Args>
void func (std::tuple<Args...> t, SomeSpecialType num = fromNum(5))
{
// don't forget to move t when you use it for the last time
}
Плюсы: интерфейс намного проще, перегружать и добавлять значения по умолчанию довольно легко.
Минусы: вызывающий должен вручную обернуть аргументы в std::make_tuple
или же std::forward_as_tuple
вызов. Кроме того, вам, вероятно, придется прибегнуть к std::index_sequence
хитрости для реализации функции.