cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
90 stars 0 forks source link

variadic template фиксированного типа #202

Closed apolukhin closed 3 years ago

apolukhin commented 3 years ago

Перенос предложения: голоса +8, -3 Автор идеи: Antervis

разрешить конструкцию типа void func(MyType ...args) { ... };

Добавить возможность писать variadic template функции, принимающие variadic pack фиксированного типа.

apolukhin commented 3 years ago

yndx-antoshkka, 5 июля 2017, 16:29 А как именно это должно выглядеть?

Павел, 5 июля 2017, 18:16 yndx-antoshkka,

void foo(const std::string&... words) {
    (std::cout << ... << words) << std::endl;
}

yndx-antoshkka, 5 июля 2017, 19:21 Павел Корозевцев, тут есть проблема:

void foo(int...);

Такой код сейчас собирается, за счёт правила, позволяющего не ставить запятую и трактуется как

void foo(int /*var*/, ...);

С variadic templates всё верно работает за счёт контекстно чувствительной грамматики

template <int... I> // compiler: remembering that `I` is a pack
void foo(I......); // compiler: `I` is a pack => void foo(I... /*vars*/, ...);

В вашем примере void foo(const std::string&...) компилятор не сможет догадаться о ваших намерениях. Нужно как-то ему намекнуть + неплохо было бы показать пользователю, что функция на самом то деле шаблонная (см ваш же комментарий ниже)

Andrey Davydov, 6 июля 2017, 0:33 yndx-antoshkka, теоретически, для этой цели по аналогии с std::initializer_list можно ввести специальный магический тип std::varargs. Тогда пример с выводом слов мог бы выглядеть так:

template<size_t N>
void foo(std::varargs<std::string const &, N> words) {
    for (auto const & w : words) std::cout << w;// constexpr for из p0589
    std::cout << std::endl;
}

Однако меня пугает, то что overload resolution усложнится еще больше, особенно при инициализации объектов.

struct Container
{
    template<typename T> Container(std::initializer_list<T>); // #1
    template<typename T> Container(std::size_t size, T value); // #2
    template<typename T, std::size_t N> Container(std::varargs<T, N>); // #3
};

Container c2 {1, 2}; // #1
Container c1 (1, 2); // #2 или #3

Antervis, 6 июля 2017, 9:16 yndx-antoshkka, разумеется, чем проще тем лучше. Идеальный вариант - void func(int ...args);

Я так полагаю, проблема в том, что дополнительно требуется явно указать что функция-шаблон (подобная проблема, если не ошибаюсь, с синтаксисом типа void func(auto arg);). Синтаксис template (без скобок) уже используется для явного инстанцирования, синтаксис template <> (без аргументов) уже используется для template overloading'а. template или template <int ...args> - очевидно нет. Что оставляет нам один вариант: template <...> - явно указать, что в шаблоне присутствуют varargs.

Тогда синтаксис будет вида

template <...>
void func(int ..args) { /*...*/ }

Павел, 5 июля 2017, 18:20 А зачем это делать? Это всё равно будет шаблон. Всё равно инстанциироваться будет на этапе компиляции, как и шаблоны. Если вы хотите гарантировать, что в функцию не попадёт "неправильный" тип, то это можно и сейчас делать.

Такой синтаксис подарит нам ограничения, но не даст фич. Или я ошибаюсь?

Andrey Davydov, 5 июля 2017, 23:15 Павел Корозевцев, гарантировать что в функцию не попадет неправильный тип можно, но заставить, чтобы вывелся правильный тип -- нет. Воображаемую функцию void foo(const std::string&... words); можно будет вызвать так:

foo("aba", "caba"),

а такую

template<typename... Args,
typename = std::enable_if_t<std::conjunction_v<std::is_same<std::string, Args>...>>>
void foo(Args const & ...);

нельзя.

Antervis, 6 июля 2017, 8:22 Павел Корозевцев, проблема в другом. Если у нас есть func(string ...s), мы сможем передать в неё func(s1, s2, {v1.begin(),v1.end()}) и код скомпилируется. С variadic template'ом последний аргумент попросту не получится передать

Александр, 6 июля 2017, 8:25 Andrey Davydov,

template<typename ... Args, typename = std::enable_if_t<std::conjunction_v<std::is_constructible<std::string, Args>...>>>
void foo(Args &&...words)
{
    (std::cout << ... << words) << std::endl;
}

Andrey Davydov, 6 июля 2017, 8:45 croessmah, в Вашем варианте все равно не заработает пример от @Antervis комментарием выше.

Александр, 7 июля 2017, 13:51 Andrey Davydov, я когда писал, его поста еще не было. Да и других проблем это не решает.

rymis, 11 июля 2017, 13:46 Удалил свой предыдущий комментарий, чтобы написать корректно.

Я недавно решал подобную проблему так:

#include <iostream>
#include <string>
#include <utility>

using namespace std;

template <typename...T>
inline void print_impl(const typename pair<T, string>::second_type&...args) {
    std::string strings[] = {args...};
    for (auto&& s : strings)
        cout << s;

    cout << endl;
}

template <typename...T>
inline void print(T&&...vals) {
    print_impl<T...>(std::forward<T>(vals)...);
}

int main() {
    print("Hello", " ", "World");
}

Так это работает вполне пристойно. Конечно выглядит не очень, но проблема решаема в рамках текущего стандарта C++.

Antervis, 12 июля 2017, 15:44 rymis, ваш пример не решает braced-initialization проблему, описанную выше:

string s = "somestring";
print({s.begin(), s.end()});

Так не сработает.

apolukhin commented 3 years ago

C С++20 terse syntax для концептов появилась возможность указывать параметры паков, как на точное соответствие

void exact(std::same_as<std::string> auto const& ... strings) {}

так и на не точное:

void inexact(std::convertible_to<std::string> auto const& ... strings) {}

Песочница https://godbolt.org/z/1ThEahxeY

braced-initialization правда не разрешён, но это возможно и к лучшему, слишком уж много неоднозначностей появляется