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

Antervis
Antervis

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

4
рейтинг
13 комментариев
yndx-antoshkka
А как именно это должно выглядеть?
yndx-antoshkka
Павел Корозевцев
yndx-antoshkka,

void foo(const std::string&... words) {
(std::cout << ... << words) << std::endl;
}
Павел Корозевцев
yndx-antoshkka
Павел Корозевцев, тут есть проблема:

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&...)` компилятор не сможет догадаться о ваших намерениях. Нужно как-то ему намекнуть + неплохо было бы показать пользователю, что функция на самом то деле шаблонная (см ваш же комментарий ниже)
yndx-antoshkka
Andrey Davydov
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
Andrey Davydov
Antervis
yndx-antoshkka, разумеется, чем проще тем лучше. Идеальный вариант - void func(int ...args);

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

Тогда синтаксис будет вида
template <...>
void func(int ..args) { /*...*/ }
Antervis
Павел Корозевцев
А зачем это делать? Это всё равно будет шаблон. Всё равно инстанциироваться будет на этапе компиляции, как и шаблоны. Если вы хотите гарантировать, что в функцию не попадёт "неправильный" тип, то это можно и сейчас делать.

Такой синтаксис подарит нам ограничения, но не даст фич. Или я ошибаюсь?
Павел Корозевцев
Andrey Davydov
Павел Корозевцев, гарантировать что в функцию не попадет неправильный тип можно, но заставить, чтобы вывелся правильный тип -- нет. Воображаемую функцию
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 & ...);
нельзя.
Andrey Davydov
Antervis
Павел Корозевцев, проблема в другом. Если у нас есть func(string ...s), мы сможем передать в неё func(s1, s2, {v1.begin(),v1.end()}) и код скомпилируется. С variadic template'ом последний аргумент попросту не получится передать
Antervis
croessmah
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;
}
croessmah
Andrey Davydov
croessmah, в Вашем варианте все равно не заработает пример от @Antervis комментарием выше.
Andrey Davydov
croessmah
Andrey Davydov, я когда писал, его поста еще не было. Да и других проблем это не решает.
croessmah
rymis
Удалил свой предыдущий комментарий, чтобы написать корректно.

Я недавно решал подобную проблему так:
#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++.
rymis
Antervis
rymis, ваш пример не решает braced-initialization проблему, описанную выше:
string s = "somestring";
print({s.begin(), s.end()});
Так не сработает.
Antervis
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).