2015-08-28 2 views
25

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

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

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

+3

Я бы предположил, что вариационные функции были добавлены в C с единственной целью поддержки семейства функций printf, которые должны быть небезопасными по типу. Сама концепция ввода-вывода в формате строки, вероятно, была взята только из предшественников C, таких как BCPL (см. Https://en.wikipedia.org/wiki/BCPL). В современном C++ нет необходимости вводить различные вариабельные функции, так как у нас есть превосходные языковые конструкции, тем более, что C++ 11. К сожалению, у меня нет ссылок на мои догадки. Было бы интересно задать этот вопрос самому Бьярну Страуступу. –

+1

Вы можете сделать 'void foo (double *)' и называть его 'foo ((double []) {1,2,3,4,5})'. Необходимо расширение GNU C++. – user3528438

+0

Неужели это особенность слишком анекдотическая, чтобы ее можно было включить в уже перегруженный язык? Тогда вы должны также утверждать, что 'void foo (double ..., int ..., double ...)' и тому подобное. –

ответ

10

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

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

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

Поскольку 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... объявить VARIADIC типа
  • Args const&... объявить пакет аргументов
  • args... для распаковки упаковки в выражении
+0

Вызов 'print' не имеет смысла. Где идут «арги»? – Quentin

+4

@Quentin: функции Variadic часто работают (сегодня) с рекурсией, поэтому вы вызываете 'print' с 4 аргументами, который вызывает' print' с 3 аргументами, который вызывает 'print' с 2 аргументами, который вызывает' print' с 1 аргументом => это базовый случай (невариантная функция) и остановка рекурсии. –

+0

Я слепой, не вижу, что это были две перегрузки 'print'. Теперь имеет смысл: p – Quentin

0

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

void foo(std::vector<T> values); 
+0

... который является безопасным по типу, но допускает только один тип ... и не очень «естественный» для вызова. – DevSolar

+0

Я серьезно сомневаюсь, что это причина. Кроме того, это совсем не то, что нужно. – juanchopanza

+0

@juan, пожалуйста уточните. OP хотел получить аналог функции с переменным количеством аргументов, причем все аргументы имеют один тип (это четко указано). Теперь, что мы имеем здесь, по сравнению с этим? Различное количество неназванных аргументов? Проверьте. Способность подсчитывать фактически переданные аргументы и получать их значения? Проверьте. Конечно, это не _reason_, однако я считаю, что функция, которая принимает вектор T, полностью соответствует требованию OPs 'Пользователь может передать переменное число терминов функции foo, однако все термины должны быть типа T' – SingerOfTheFall

1

путь к достижению (своего рода), что вы предлагаете, чтобы использовать вариативные шаблоны

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

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

+0

Это не так сложно реализовать. На самом деле это проще, чем вариационные шаблоны. Однако в документе должно (A) включить обоснование, почему это необходимо, и, следовательно, почему 'std :: initializer_list ' не будет делать, и, вероятно, (B) объяснить, как 'sizeof ...' работает для' double .. .'. Бонусные баллы за (C) фактическую предложенную формулировку для Стандарта. – MSalters

19

Существует

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 
} 

, но тип параметра шаблона не-типа (ммм) имеет некоторые ограничения: он не может быть double, например.

+4

, а значение параметра (ов) непигового шаблона имеет ограничения - они должны быть постоянными выражениями времени компиляции. –

10

уже можно с переменным числом шаблонов и 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 в C++ 17.

Как позже и будущие стандарты сосредоточены на terser синтаксиса (немногословный для петель, неявных шаблонов функций ...) очень возможно, что предлагаемый вами синтаксис заканчивается в стандартном один день)

+0

Я не могу заставить его работать: 'ошибка C2783: 'void foo (Doubles ...)': не удалось вывести аргумент шаблона для ''' –

+0

@SimonKraemer IIRC SFINAE - это все виды сломанных на MSVC, но, к сожалению, у меня нет опыта в поиске обходных решений ... – Quentin

+0

Даже в VS2015? Хорошо ... Тем временем я сам создаю «обходной путь»: http://stackoverflow.com/a/32273936/4181011 –

2

Для почему в частности, такая вещь не предлагалась (или была предложена и отвергнута), я не знаю. Такая вещь, безусловно, была бы полезна, но добавила бы больше сложности для языка. Как показывает Quentin, уже существует способ C++ 11 для достижения такой цели с помощью шаблонов.

Когда понятия добавляется к стандарту, мы будем иметь еще один, более краткий способ:

template <Convertible<double>... Args> 
void foo(Args... doubles); 

или

template <typename... Args> 
    requires Convertible<Args, double>()... 
void foo(Args... doubles); 

или, как @dyp points out:

void foo(Convertible<double>... doubles);  

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

+1

Или просто 'void foo (Convertible ... doubles);' (который мне пришлось искать, но гарантированно работает N4377, причина, вероятно, в том, что любая функция с placeholder в параметре-decl-clause * эквивалент * в /, определяемый как шаблон функции) – dyp

+0

@dyp Лично я не являюсь поклонником этого использования, поскольку теперь мы скрываем, что это шаблон функции. Хотя я с нетерпением жду всей репутации, я получу, отвечая на вопросы SO об этом :) – Barry

+0

О, мы можем сделать это * полностью ясно *, что это шаблон с помощью * pretty * 'AllConvertibleToDouble {... T} void foo (T ... t); '- я просто не могу получить gcc, чтобы принять' AllConvertible {... T} void foo (T ... t); 'к сожалению :(- hmm теперь я хочу определить концепцию под названием «Template» – dyp

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

на основе Matthew's answer:

void foo() {} 

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

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