Можно ли сложить только часть пакета с C ++ 17 выражениями? - PullRequest
5 голосов
/ 24 сентября 2019

Я пытаюсь выяснить, как сложить только часть пакета шаблонов с вариациями с помощью C ++ 17 выражений.Предположим, я хотел бы создать «разделенную строку» времени компиляции, например "str1_str2_str3_....."

. Это можно довольно легко сделать с помощью такого кода (просто пример):

std::string result;

template<class... Strings>
ConcatString(Strings&... strs)
{
    (ConcatString(strs), ...);
}

template <class String>
void ConcatString(String& str)
{
     result += str + "_"
}

Тогдамы можем выполнить его так:

std::string s1 = "s1", s2 = "s2", s3 = "s3";
ConcatString(s1, s2, s3);
// result == "s1_s2_s3_"

Как вы можете видеть, есть проблема с последним разделителем.Есть ли способ избежать этой проблемы без проверок во время выполнения?Одно из решений, которое я могу себе представить, - сложить только (N - 1) аргов и объединить последние «вручную».

Ответы [ 3 ]

8 голосов
/ 24 сентября 2019

Вы можете вызвать ConcatString рекурсивно и использовать constexpr, если избегаете проверок во время выполнения

template<typename String, typename... Strings>
void ConcatString(String& str, Strings&... strs) {
    if constexpr(sizeof...(strs) == 0) {
        result += str;
    } else {
        result += str + "_";
        ConcatString(strs...);
    }
}
7 голосов
/ 24 сентября 2019

Можно ли сложить только часть пакета с 17-кратными выражениями C ++?

Нет, выражение сгиба будет складываться по всей упаковке.Тем не менее, мы можем сделать несколько трюков для достижения того, что нам нужно.

Здесь ваша функция Concat может быть упрощена для использования одного двоичного сгиба.

template<class String, class... Strings>
std::string Concat(const std::string& delimiter, const String& str, const Strings&... strs)
{
     return (str + ... + (delimiter + strs));
}

Использование:

int main()
{
    std::cout << Concat(",", "a") << std::endl;
    std::cout << Concat(",", "a", "b") << std::endl;
    std::cout << Concat(",", "a", "b", "c") << std::endl;
}

Выход:

a
a, b
a, b, c

Live Demo

Хитрость в том, что мы разбиваем пакет параметров на единичную «голову» (str) и вариационный «хвост» (strs).Таким образом, мы позволяем списку параметров функции вытащить первый элемент из пакета.(A lot метапрограммирования шаблонов в стиле C ++ 11 использовал этот трюк).

Другой подход - создать набор индексов 0, 1, ..., N длянаш пакет параметров и затем для нашей логики свертывания мы могли бы сделать что-то особенное для 0-го, N-го или даже произвольного элемента.Вы можете найти варианты этого подхода на вопросе pretty print tuple .В C ++ 20 благодаря лямбда-шаблонам в C ++ 20 мы можем переместить всю логику в один метод, например, так:

template<class... Strings>
std::string Concat(const std::string& delimiter, const Strings&... strs)
{
    return [&delimiter]<class Tup, size_t... I> (const Tup& tuple, std::index_sequence<I...>)
    {
        return (std::string{} + ... + (I == 0 ? std::get<I>(tuple) : delimiter + std::get<I>(tuple)));
    }(std::tie(strs...), std::make_index_sequence<sizeof...(strs)>{});
}

Demo 2

Этот уродливый синтаксис - это я создаю лямбду и вызываю ее в одном выражении.Возможно, вы можете сделать его более читабельным, вызывая его с помощью std::invoke.

Обратите внимание, что мы используем проверку индекса на предмет того, печатать ли разделитель или нет.

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

template<class... Strings>
std::string ConcatEveryOther(const std::string& delimiter, const Strings&... strs)
{
    return [&delimiter]<class Tup, size_t... I> (const Tup& tuple, std::index_sequence<I...>)
    {
        return (std::string{} + ... + (I % 2 == 0 ? std::get<I>(tuple) : delimiter + std::get<I>(tuple)));
    }(std::tie(strs...), std::make_index_sequence<sizeof...(strs)>{});
}

Теперь std::cout << ConcatEveryOther(",", "a", "b", "c", "d", "e", "f", "g") << std::endl; даст нам вывод, подобный

a, до н.э., де, фг

Демо 3

2 голосов
/ 24 сентября 2019

Как насчет:

template<typename Last>
std::string concat(Last last) {
    return last;
}

template<typename First, typename ...Rest>
std::string concat(First first, Rest ...rest) {
    return first + '_' + concat(rest...);
}

std::string s1{"foo"}, s2{"bar"}, s3{"baz"};
std::cout << concat(s1, s2, s3); // Prints "foo_bar_baz"

РЕДАКТИРОВАТЬ: я сделал функцию еще проще:

template <typename First, typename... Rest>
std::string concat(First first, Rest... rest) {
    return (first + ... + ('_' + rest));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...