开发者

How to implement "Variadic Template" with pre-c++0x(VS2008)?

开发者 https://www.devze.com 2023-04-11 03:15 出处:网络
I\'m using Visual Studio 2008, and I want to implement string formatting function without Variable Argument List.

I'm using Visual Studio 2008, and I want to implement string formatting function without Variable Argument List.

How to implement "Variadic Template" with pre-c++0x(VS2008)?

Is there any library which implements this like boost?

Or another way to implement this?

Here is my sample code. (of course, this can't be complied because i'm using 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;
        i开发者_如何学编程f(prev_offset >= s.length)
            break;
    }
    ASSERT(!"Extra Argument Provided!");
    return false;
}


In C++03, you have different possibilities:

  1. generate overloads for 0-N arguments (using Boost.Preprocessor for example)
  2. use Cons-Lists (cons(1)("some string")(foo))
  3. use object and overload some operator (operator() for example, or operator% like Boost.Format)

The first option is a bit tricky, I feel, because not everyone can understand macros easily, so I would only reserve it for short-terms solutions if you plan on migrating to C++0x soon.

The third option may provide a nice custom touch (formatting is done with a % sign in many languages), but it also means that one needs to remember how this particular "variadic" function works each time.

My personal preference is the cons approach because it solves both issues:

  • the definition involves only templates, so it is more readable and maintanable than 1.
  • you define the cons-machinery once, and you can then re-use it for any "variadic" function (and they remain functions), so it is more consistent, and saves you work

For example, here is how it could work:

The includes that this example will use:

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

A helper for the result type of appending a value (it could be more efficient with prepending, but that would mean passing the arguments in reverse order which is counter-intuitive):

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;
};

The Cons template itself, with a magic operator() to append value. Note that it creates a new item with a different type:

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());
}

A revisited VarPrint with it:

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;
}

And the demo:

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

Output:

integer 1
mix of 2 and foo


There is no variadic template functionality in C++03. Boost and other well designed libraries work around this in different ways. For functions, one can have a number of N + 1 overloads where each overload take from 0 to N arguments. For classes, one can have a single definition with up to N arguments that default to some invalid type. This higher limits are usually configurable via some macro; because setting it to high will impose an overhead in compile times, and setting it to low will cause users not being able to pass enough arguments.

For your particular case, I would implement VarPrint in a recursive way. Each step up in the recursion will process a single argument, and issue a recursive call with a modified format string and all the left values shifted left one position.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号