variadic функции — Почему C ++ не поддерживает сильно типизированный многоточие?

Может кто-нибудь объяснить мне, почему C ++, по крайней мере, насколько мне известно, не реализует сильно типизированную функцию многоточия, что-то вроде:

void foo(double ...) {
// Do Something
}

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

29

Решение

Исторически синтаксис многоточия ... происходит от C.

Этот сложный зверь был использован для власти printf-подобные функции и должен использоваться с va_list, va_start так далее…

Как вы заметили, это небезопасно; но тогда C далек от того, чтобы быть безопасным, что с его неявными преобразованиями из и в void* для любых типов указателей, его неявное усечение интегралов / значений с плавающей запятой и т. д.

Поскольку C ++ должен был быть как можно ближе к надмножеству C, он унаследовал многоточие от C.


С самого начала практика C ++ развивалась, и был сильный толчок к более строгой типизации.

В C ++ 11 это завершилось:

  • списки инициализаторов, сокращенный синтаксис для переменного числа значений данного типа: foo({1, 2, 3, 4, 5})
  • шаблоны variadic, которые являются собственным зверем и позволяют писать безопасные для типов 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... распаковать пачку в выражении
15

Другие решения

Есть

 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, например.

23

Это уже возможно с шаблонами 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, неявные шаблоны функций …), вполне возможно, что предложенный вами синтаксис окажется в Стандарте однажды;)

11

Почему конкретно такая вещь не была предложена (или была предложена и отклонена), я не знаю. Такая вещь, безусловно, будет полезна, но добавит сложности языку. Как 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);

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

2

Способ достижения (своего рода) того, что вы предлагаете, — это использование шаблонов с переменными параметрами.

template<typename... Arguments>
void foo(Arguments... parameters);

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

1
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)
{
}
1

На основе Ответ Мэтью:

void foo () {}

template <typename... Rest>
void foo (double arg, Rest... rest)
{
/* do something with arg */
foo(rest...);
}

Если код с помощью foo компилирует, вы знаете, все аргументы могут быть преобразованы в double,

1

Потому что вы можете использовать

void foo(std::vector<T> values);
0
По вопросам рекламы [email protected]