Как реализовать "шаблон Variadic" с pre-c ++ 0x (VS2008)? - PullRequest
9 голосов
/ 07 октября 2011

Я использую Visual Studio 2008 и хочу реализовать функцию форматирования строки без Переменного списка аргументов .

Как реализовать шаблон Variadic с pre-c ++ 0x (VS2008)?

Есть ли библиотека, которая реализует это как Boost?

Или другой способ реализовать это?

Вот мой пример кода. (конечно, это не может быть выполнено, потому что я использую VS2008.)

bool VarPrint(std::ostringstream& out, const std::string& s) 
{
    std::string::size_type offset = 0;
    if((offset = s.find("%")) != std::string::npos)
    {
        if(!(offset != s.size() - 1 && s[offset + 1] == '%'))
        {
            ASSERT(!"Missing Arguments!");
            return false;
        }
    }
    out << s;
    return true;
}

template<typename T, typename... Args>
bool VarPrint(std::ostringstream& out, const std::string& s, const T& value, const Args&... args) 
{
    std::string::size_type prev_offset = 0;
    std::string::size_type curr_offset = 0;
    while((curr_offset = s.find("%", prev_offset)) != std::string::npos)
    {
        out << s.substr(prev_offset, curr_offset);
            if(!(curr_offset != s.size() - 1 && s[curr_offset + 1] == '%'))
        {
            out << value;
            if(curr_offset + 2 < s.length())
                return VarPrint(out, s.substr(curr_offset + 2), args...);                   return true;
        }

        prev_offset = curr_offset + 2;
        if(prev_offset >= s.length)
            break;
    }
    ASSERT(!"Extra Argument Provided!");
    return false;
}

Ответы [ 2 ]

18 голосов
/ 07 октября 2011

В C ++ 03 у вас есть разные возможности:

  1. генерирует перегрузки для 0-N аргументов (например, с использованием Boost.Preprocessor)
  2. использовать Cons-Lists (cons(1)("some string")(foo))
  3. использовать объект и перегружать некоторый оператор (например, operator() или operator%, как Boost.Format)

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

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

Мое личное предпочтение - это подход cons, потому что он решает обе проблемы:

  • определение включает только шаблоны, поэтому оно более читабельно и доступно для обработки, чем 1.
  • Вы один раз определяете аппарат-механизм, а затем можете повторно использовать его для любой «вариадической» функции (и они остаются функциями), так что она более согласованна и экономит вашу работу

Например, вот как это может работать:

Включения, которые будет использовать этот пример:

#include <cassert>
#include <iostream>
#include <string>

Помощник для типа результата добавления значения (это может быть более эффективным при добавлении, но это будет означать передачу аргументов в обратном порядке, что нелогично):

template <typename T, typename Next> struct Cons;
struct ConsEmpty;

template <typename Cons, typename U>
struct cons_result;

template <typename U>
struct cons_result<ConsEmpty, U> {
  typedef Cons<U, ConsEmpty> type;
};

template <typename T, typename U>
struct cons_result<Cons<T, ConsEmpty>, U> {
  typedef Cons<T, Cons<U, ConsEmpty> > type;
};

template <typename T, typename Next, typename U>
struct cons_result<Cons<T, Next>, U> {
  typedef Cons<T, typename cons_result<Next, U>::type> type;
};

Сам шаблон Cons, с волшебным operator() для добавления значения. Обратите внимание, что создается новый элемент другого типа:

template <typename T, typename Next>
struct Cons {
  Cons(T t, Next n): value(t), next(n) {}

  T value;
  Next next;

  template <typename U>
  typename cons_result<Cons, U>::type operator()(U u) {
    typedef typename cons_result<Cons, U>::type Result;
    return Result(value, next(u));
  }
};

struct ConsEmpty {
  template <typename U>
  Cons<U, ConsEmpty> operator()(U u) {
    return Cons<U, ConsEmpty>(u, ConsEmpty());
  }
};

template <typename T>
Cons<T, ConsEmpty> cons(T t) {
  return Cons<T, ConsEmpty>(t, ConsEmpty());
}

Повторно VarPrint с ним:

bool VarPrint(std::ostream& out, const std::string& s, ConsEmpty) {
    std::string::size_type offset = 0;
    if((offset = s.find("%")) != std::string::npos) {
        if(offset == s.size() - 1 || s[offset + 1] != '%')  {
            assert(0 && "Missing Arguments!");
            return false;
        }
    }
    out << s;
    return true;
}

template<typename T, typename Next>
bool VarPrint(std::ostream& out,
              std::string const& s,
              Cons<T, Next> const& cons) 
{
    std::string::size_type prev_offset = 0, curr_offset = 0;
    while((curr_offset = s.find("%", prev_offset)) != std::string::npos) {
        out << s.substr(prev_offset, curr_offset);
        if(curr_offset == s.size() - 1 || s[curr_offset + 1] != '%') {
            out << cons.value;
            if(curr_offset + 2 < s.length())
                return VarPrint(out, s.substr(curr_offset + 2), cons.next);
            return true;
        }
        prev_offset = curr_offset + 2;
        if(prev_offset >= s.length())
            break;
    }
    assert(0 && "Extra Argument Provided!");
    return false;
}

И демоверсия:

int main() {
  VarPrint(std::cout, "integer %i\n", cons(1));
  VarPrint(std::cout, "mix of %i and %s\n", cons(2)("foo"));
}

Выход:

integer 1
mix of 2 and foo
6 голосов
/ 07 октября 2011

В C ++ 03 нет функциональности шаблонов с переменными параметрами.Boost и другие хорошо спроектированные библиотеки работают по-разному.Для функций можно иметь несколько перегрузок N + 1, где каждая перегрузка принимает от 0 до N аргументов.Для классов можно иметь одно определение, содержащее до N аргументов, которые по умолчанию имеют недопустимый тип.Эти более высокие пределы обычно настраиваются с помощью некоторого макроса;потому что установка его на высокий уровень приведет к накладным расходам во время компиляции, а установка на низкий уровень приведет к тому, что пользователи не смогут передавать достаточно аргументов.

В вашем конкретном случае я бы реализовал VarPrint рекурсивным способом,Каждый шаг в рекурсии обрабатывает один аргумент и выполняет рекурсивный вызов с измененной строкой формата, и все левые значения сдвигаются влево на одну позицию.

...