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

"интерполяция строк" ​​в С++: построить std::string со встроенными значениями (например, для сообщений об ошибках)?

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

В C я бы сделал что-то вроде этого:

printf("error! value was %d but I expected %d",actualValue,expectedValue)

тогда как если бы я программировал в python, я бы сделал что-то вроде этого:

"error! value was {0} but I expected {1}".format(actualValue,expectedValue)

оба являются примерами строковой интерполяции.

Как это сделать в С++?

Важные предостережения:

  • Я знаю, что я могу использовать std::cout, если я хочу напечатать такое сообщение на стандартный вывод (а не на строку интерполяции, но распечатывает нужный тип строки):
cout << "error! value was " << actualValue << " but I expected "
<< expectedValue;

Я не хочу печатать строку в stdout. Я хочу передать std::string в качестве аргумента функции (например, конструктор объекта исключения).

  1. Я использую С++ 11, но переносимость потенциально является проблемой, поэтому знание того, какие методы работают и не работают, в каких версиях С++ будет плюсом.

Edit

  1. Для моего непосредственного использования меня не интересует производительность (я поднимаю исключение для крика вслух!). Однако знание относительной производительности различных методов было бы очень полезно в целом.

  2. Почему бы просто не использовать printf (С++ - это супермножество C в конце концов...)? В этом ответе обсуждаются некоторые причины, почему нет. Насколько я понимаю, тип безопасности - это большая причина: если вы положили% d, переменная, которую вы ввели, лучше всего была бы конвертируемой в целое число, так как функция определяет, какой тип она есть. Было бы гораздо безопаснее иметь метод, который использует знание времени компиляции фактического типа переменных, которые нужно вставить.

4b9b3361

Ответ 1

Способ 1: использование потока строк

Похоже, std::stringstream дает быстрое решение:

std::stringstream ss;
ss << "error! value was " << actualValue << " but I expected " <<  expectedValue << endl;

//example usage
throw MyException(ss.str())

положительный

  • нет внешних зависимостей
  • Я считаю, что это работает как в C++ 03, так и в C++ 11.

отрицательный

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

Способ 2: повышение формата

Библиотека Boost Format также возможна. Используя это, вы бы сделали:

throw MyException(boost::format("error! value was %1% but I expected %2%") % actualValue % expectedValue);

положительный

  • довольно чистый по сравнению с методом stringstream: одна компактная конструкция

отрицательный

  • как сообщается, довольно медленный: использует потоковый метод внутри
  • это внешняя зависимость

Редактировать:

Способ 3: параметры шаблона

Похоже, что типобезопасная версия printf может быть создана с помощью переменных параметров шаблона (технический термин для шаблона, который принимает неопределенное количество параметров шаблона). Я видел ряд возможностей в этом ключе:

  • Этот вопрос дает компактный пример и обсуждает проблемы производительности с этим примером.
  • Этот ответ на этот вопрос, реализация которого также довольно компактна, но, по сообщениям, все еще страдает от проблем с производительностью.
  • Библиотека fmt, обсуждаемая в этом ответе, по сообщениям, довольно быстрая и, кажется, такая же чистая, как и сам printf, но является внешней зависимостью

положительный

  • использование чисто: просто вызовите функцию, похожую на printf
  • Библиотека FMT по сообщениям довольно быстро
  • Другие параметры кажутся довольно компактными (не требуется никаких внешних зависимостей)

отрицательный

  • библиотека fmt, хотя и быстрая, является внешней зависимостью
  • другие варианты, по-видимому, имеют некоторые проблемы с производительностью

Ответ 2

В С++ 11 вы можете использовать std::to_string:

"error! value was " + std::to_string(actualValue) + " but I expected " + std::to_string(expectedValue)

Это не очень, но это просто, и вы можете использовать макрос, чтобы немного уменьшить его. Производительность невелика, так как вы заранее не занимаете пространство reserve(). Шаблоны Variadic, вероятно, будут быстрее и выглядят лучше.

Этот тип строковой конструкции (вместо интерполяции) также плохо для локализации, но вы, вероятно, использовали бы библиотеку, если бы вам это нужно.

Ответ 3

Используйте все, что вам нравится:

1) std:: stringstream

#include <sstream>
std::stringstream ss;
ss << "Hello world!" << std::endl;
throw std::runtime_error(ss.str());

2) libfmt: https://github.com/fmtlib/fmt

#include <stdexcept>
throw std::runtime_error(
    fmt::format("Error has been detected with code {} while {}",
        0x42, "copying"));

Ответ 4

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Следующий код основан на статье, которую я прочитал 2 года назад. Я найду источник и выложу его как можно скорее.

Это то, что я использую в своем проекте C++ 17. Должен работать с любым компилятором C++, поддерживающим шаблоны переменных.

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

std::string const word    = "Beautiful";
std::string const message = CString::format("%0 is a %1 word with %2 characters.\n%0 %2 %0 %1 %2", word, "beautiful", word.size()); 
// Prints:
//   Beautiful is a beautiful word with 9 characters. 
//   Beautiful 9 Beautiful beautiful 9.

Реализация класса:

/**
 * The CString class provides helpers to convert 8 and 16-bit
 * strings to each other or format a string with a variadic number
 * of arguments.
 */
class CString
{
    public:
    /**
     * Format a string based on 'aFormat' with a variadic number of arbitrarily typed arguments.
     *
     * @param aFormat
     * @param aArguments
     * @return
     */
    template <typename... TArgs>
    static std::string format(
            std::string const&aFormat,
            TArgs        &&...aArguments);

    /**
     * Accept an arbitrarily typed argument and convert it to it proper
     * string representation.
     *
     * @tparam TArg
     * @tparam TEnable
     * @param aArg
     * @return
     */
    template <
            typename TArg,
            typename TEnable = void
            >
    static std::string toString(TArg const &aArg);

    /**
     * Accept a float argument and convert it to it proper string representation.
     *
     * @tparam TArg
     * @param arg
     * @return
     */
    template <
            typename TArg,
            typename std::enable_if<std::is_floating_point<TArg>::value, TArg>::type
            >
    static std::string toString(const float& arg);


    /**
     * Convert a string into an arbitrarily typed representation.
     *
     * @param aString
     * @return
     */
    template <
            typename TData,
            typename TEnable = void
            >
    static TData const fromString(std::string const &aString);


    template <
            typename TData,
            typename std::enable_if
                     <
                        std::is_integral<TData>::value || std::is_floating_point<TData>::value,
                        TData
                     >::type
            >
    static TData fromString(std::string const &aString);

private:
    /**
     * Format a list of arguments. In this case zero arguments as the abort-condition
     * of the recursive expansion of the parameter pack.
     *
     * @param aArguments
     */
    template <std::size_t NArgs>
    static void formatArguments(std::array<std::string, NArgs> const &aArguments);

    /**
     * Format a list of arguments of arbitrary type and expand recursively.
     *
     * @param outFormatted
     * @param inArg
     * @param inArgs
     */
    template <
            std::size_t NArgs,
            typename    TArg,
            typename... TArgs
            >
    static void formatArguments(
            std::array<std::string, NArgs>     &aOutFormatted,
            TArg                              &&aInArg,
            TArgs                          &&...aInArgs);
};
//<-----------------------------------------------------------------------------

//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <typename... TArgs>
std::string CString::format(
        const std::string     &aFormat,
        TArgs             &&...aArgs)
{
    std::array<std::string, sizeof...(aArgs)> formattedArguments{};

    formatArguments(formattedArguments, std::forward<TArgs>(aArgs)...);

    if constexpr (sizeof...(aArgs) == 0)
    {
        return aFormat;
    }
    else {
        uint32_t number     = 0;
        bool     readNumber = false;

        std::ostringstream stream;

        for(std::size_t k = 0; k < aFormat.size(); ++k)
        {
            switch(aFormat[k])
            {
            case '%':
                readNumber = true;
                break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                // Desired behaviour to enable reading numbers in text w/o preceding %
                #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
                if(readNumber)
                {
                    number *= 10;
                    number += static_cast<uint32_t>(aFormat[k] - '0');
                    break;
                }
            default:
                if(readNumber)
                {
                    stream << formattedArguments[std::size_t(number)];
                    readNumber = false;
                    number     = 0;
                }

                stream << aFormat[k];
                break;
                #pragma GCC diagnostic warning "-Wimplicit-fallthrough"
            }
        }

        if(readNumber)
        {
            stream << formattedArguments[std::size_t(number)];
            readNumber = false;
            number     = 0;
        }

        return stream.str();
    }
}
//<-----------------------------------------------------------------------------

//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <typename TArg, typename enable>
std::string CString::toString(TArg const &aArg)
{
    std::ostringstream stream;
    stream << aArg;
    return stream.str();
}
//<-----------------------------------------------------------------------------

//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <
        typename TArg,
        typename std::enable_if<std::is_floating_point<TArg>::value, TArg>::type
        >
std::string CString::toString(const float& arg) {
    std::ostringstream stream;
    stream << std::setprecision(12) << arg;
    return stream.str();
}
//<-----------------------------------------------------------------------------

//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <std::size_t argCount>
void CString::formatArguments(std::array<std::string, argCount> const&aArgs)
{
    // Unused: aArgs
}
//<-----------------------------------------------------------------------------

//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <std::size_t argCount, typename TArg, typename... TArgs>
void CString::formatArguments(
        std::array<std::string, argCount>     &outFormatted,
        TArg                                 &&inArg,
        TArgs                             &&...inArgs)
{
    // Executed for each, recursively until there no param left.
    uint32_t const index = (argCount - 1 - sizeof...(TArgs));
    outFormatted[index] = toString(inArg);

    formatArguments(outFormatted, std::forward<TArgs>(inArgs)...);
}
//<-----------------------------------------------------------------------------

//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <
        typename TData,
        typename std::enable_if
                 <
                    std::is_integral<TData>::value || std::is_floating_point<TData>::value,
                    TData
                 >::type
        >
TData CString::fromString(std::string const &aString)
{
    TData const result{};

    std::stringstream ss(aString);
    ss >> result;

    return result;
}
//<-----------------------------------------------------------------------------

}