Подтвердить что ты не робот

Почему С++ не поддерживает строго типизированный эллипс?

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

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

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

4b9b3361

Ответ 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);