Может кто-нибудь объяснить мне, почему C ++, по крайней мере, насколько мне известно, не реализует сильно типизированную функцию многоточия, что-то вроде:
void foo(double ...) {
// Do Something
}
Это означает, что, говоря простым языком: «Пользователь может передать переменное число терминов в функцию foo, однако все термины должны быть двойными»
Исторически синтаксис многоточия ...
происходит от C.
Этот сложный зверь был использован для власти printf
-подобные функции и должен использоваться с va_list
, va_start
так далее…
Как вы заметили, это небезопасно; но тогда C далек от того, чтобы быть безопасным, что с его неявными преобразованиями из и в void*
для любых типов указателей, его неявное усечение интегралов / значений с плавающей запятой и т. д.
Поскольку C ++ должен был быть как можно ближе к надмножеству C, он унаследовал многоточие от C.
С самого начала практика C ++ развивалась, и был сильный толчок к более строгой типизации.
В C ++ 11 это завершилось:
foo({1, 2, 3, 4, 5})
printf
напримерVariadic шаблоны на самом деле повторно использовать многоточие ...
в их синтаксисе, чтобы обозначить пакеты типов или значений и как оператор распаковки:
void print(std::ostream&) {}
template <typename T, typename... Args>
void print(std::ostream& out, T const& t, Args const&... args) {
print(out << t, args...); // recursive, unless there are no args left
// (in that case, it calls the first overload
// instead of recursing.)
}
Обратите внимание на 3 различных использования ...
:
typename...
объявить тип переменнойArgs const&...
объявить пакет аргументовargs...
распаковать пачку в выраженииЕсть
void foo(std::initializer_list<double> values);
// foo( {1.5, 3.14, 2.7} );
что очень близко к этому.
Вы также можете использовать шаблоны с переменным числом аргументов, но это становится более дискурсивным. Что касается фактической причины, я бы сказал, что попытка ввести этот новый синтаксис, вероятно, не стоит: как вы получаете доступ к отдельным элементам? Как вы знаете, когда остановиться? Что делает это лучше, чем, скажем, std::initializer_list
?
В C ++ есть кое-что даже ближе к этому: нетиповые пакеты параметров.
template < non-type ... values>
как в
template <int ... Ints>
void foo()
{
for (int i : {Ints...} )
// do something with i
}
но тип параметра шаблона нетипичного типа (uhm) имеет некоторые ограничения: он не может быть double
, например.
Это уже возможно с шаблонами variadic и SFINAE:
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
template <class... Doubles, class = std::enable_if_t<
all_true<std::is_convertible<Doubles, double>{}...>{}
>>
void foo(Doubles... args) {}
Благодаря Columbo для хорошего all_true
трюк. Вы также сможете использовать выражение сгиба в C ++ 17.
Поскольку более поздние и будущие стандарты фокусируются на более кратком синтаксисе (краткие циклы for, неявные шаблоны функций …), вполне возможно, что предложенный вами синтаксис окажется в Стандарте однажды;)
Почему конкретно такая вещь не была предложена (или была предложена и отклонена), я не знаю. Такая вещь, безусловно, будет полезна, но добавит сложности языку. Как Quentin демонстрирует, что уже предлагается C ++ 11 способ достижения такой цели с помощью шаблонов.
Когда Concepts будет добавлен в стандарт, у нас будет другой, более краткий способ:
template <Convertible<double>... Args>
void foo(Args... doubles);
или же
template <typename... Args>
requires Convertible<Args, double>()...
void foo(Args... doubles);
или, как @dyp указывает:
void foo(Convertible<double>... doubles);
Лично, между текущим решением и теми, которые мы получим с помощью Концепций, я считаю, что это адекватное решение проблемы. Тем более, что последний в основном то, о чем вы изначально просили.
Способ достижения (своего рода) того, что вы предлагаете, — это использование шаблонов с переменными параметрами.
template<typename... Arguments>
void foo(Arguments... parameters);
однако вы можете передать любой тип в пакете параметров сейчас.
То, что вы предлагаете, никогда не было реализовано, может быть, это может быть отличным дополнением к языку, или это может быть слишком сложно для реализации в нынешних условиях. Вы всегда можете попробовать написать предложение и отправить его на isocpp.org.
template<typename T, typename... Arguments>
struct are_same;
template <typename T, typename A1, typename... Args>
struct are_same<T, A1, Args...>{ static const bool value = std::is_same<T, A1>::value && are_same<T, Args...>::value;};
template <typename T>
struct are_same<T>{static const bool value = true;};
template<typename T, typename... Arguments>
using requires_same = std::enable_if_t<are_same<T, Arguments...>::value>;
template <typename... Arguments, typename = requires_same<double, Arguments...>>
void foo(Arguments ... parameters)
{
}
На основе Ответ Мэтью:
void foo () {}
template <typename... Rest>
void foo (double arg, Rest... rest)
{
/* do something with arg */
foo(rest...);
}
Если код с помощью foo
компилирует, вы знаете, все аргументы могут быть преобразованы в double
,
Потому что вы можете использовать
void foo(std::vector<T> values);