Почему С++ не поддерживает строго типизированный эллипс?
Может кто-нибудь объяснит мне, почему С++, по крайней мере, насколько мне известно, не реализует строго типизированную функцию многоточия, что-то вроде:
void foo(double ...) {
// Do Something
}
Это означает, что в обычном режиме говорят: "Пользователь может передать переменное число терминов в функцию foo, однако все термины должны быть удвоены"
Ответы
Ответ 1
Исторически синтаксис эллипсиса ...
происходит от C.
Этот сложный зверь использовался для питания printf
-подобных функций и должен использоваться с va_list
, va_start
и т.д.
Как вы отметили, он не является типичным; но тогда C далеко не является типичным, что с его неявными преобразованиями от и до void*
для любых типов указателей, его неявное усечение интегралов/значений с плавающей запятой и т.д.
Поскольку С++ должен быть как можно ближе к супермножеству C, он унаследовал многоточие от C.
С момента своего создания, практики С++ эволюционировали, и был сильный толчок к усилению набора текста.
В С++ 11 это привело к следующему:
- списки инициализаторов, короткий синтаксис для переменного количества значений данного типа:
foo({1, 2, 3, 4, 5})
- variadic templates, которые являются их собственными животными и позволяют писать безопасный тип
printf
, например
Вариадические шаблоны фактически повторно используют эллипсис ...
в своем синтаксисе, чтобы обозначать пакеты типов или значений и как оператор распаковки:
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...
, чтобы распаковать пакет в выражение
Ответ 2
Существует
void foo(std::initializer_list<double> values);
// foo( {1.5, 3.14, 2.7} );
который очень близок к этому.
Вы также можете использовать вариативные шаблоны, но он становится более дискурсивным. Что касается фактической причины, я бы сказал, что попытка привнести этот новый синтаксис, вероятно, не стоит того: как вы получаете доступ к отдельным элементам? Откуда вы знаете, когда остановиться? Что делает его лучше, чем, скажем, std::initializer_list
?
У С++ есть что-то еще более близкое к этому: пакеты параметров не-типа.
template < non-type ... values>
как в
template <int ... Ints>
void foo()
{
for (int i : {Ints...} )
// do something with i
}
но тип параметра шаблона не-типа (uhm) имеет некоторые ограничения: он не может быть double
, например.
Ответ 3
Это уже возможно с вариационными шаблонами и 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
. Вы также сможете использовать выражение fold в С++ 17.
Так как более поздние и предстоящие стандарты фокусируются на синтаксисе синтаксиса (краткие for-loops, неявные шаблоны функций...), очень возможно, что ваш предложенный синтаксис заканчивается в стандарте один день;)
Ответ 4
Зачем конкретно такое предложение не предлагалось (или было предложено и отклонено), я не знаю. Такая вещь, безусловно, была бы полезна, но добавила бы больше сложности для языка. Как показывает Quentin, уже существует способ С++ 11 достичь такой вещи с помощью шаблонов.
Когда понятия добавляются в стандарт, у нас будет другой, более сжатый способ:
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);
Лично, между текущим решением и теми, которые мы получим с концепциями, я считаю, что это адекватное решение проблемы. Тем более, что последнее - это то, о чем вы вначале просили.
Ответ 5
Способ достижения (своего рода) того, что вы предлагаете, - использовать вариативные шаблоны
template<typename... Arguments>
void foo(Arguments... parameters);
однако вы можете передать любой тип в пакете параметров.
То, что вы предлагаете, никогда не было реализовано, может быть, это может быть отличным дополнением к языку, или это может быть слишком сложно реализовать, поскольку это стоит. Вы всегда можете попытаться написать предложение и отправить его на isocpp.org
Ответ 6
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)
{
}
Ответ 7
На основе Ответ Мэтью:
void foo () {}
template <typename... Rest>
void foo (double arg, Rest... rest)
{
/* do something with arg */
foo(rest...);
}
Если код с использованием foo
компилируется, вы знаете, что все аргументы конвертируются в double
.
Ответ 8
Поскольку вы можете использовать
void foo(std::vector<T> values);