10

我正在使用 Visual Studio 2008,我想在没有 Variable Argument List的情况下实现字符串格式化功能。

如何使用 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;
}
4

2 回答 2

18

在 C++03 中,您有不同的可能性:

  1. 为 0-N 参数生成重载(例如使用 Boost.Preprocessor)
  2. 使用 Cons-Lists ( cons(1)("some string")(foo))
  3. 使用对象并重载一些运算符(operator()例如,或operator%像 Boost.Format)

我觉得第一个选项有点棘手,因为不是每个人都能轻松理解宏,所以如果您计划很快迁移到 C++0x,我只会将其保留用于短期解决方案。

第三个选项可能会提供一个很好的自定义触摸(格式化是用%许多语言的符号完成的),但这也意味着人们需要记住这个特定的“可变参数”函数每次是如何工作的。

我个人的偏好是这种cons方法,因为它解决了这两个问题:

  • 该定义仅涉及模板,因此它比 1 更具可读性和可维护性。
  • 您定义了一次 cons-machinery,然后您可以将它重新用于任何“可变参数”函数(并且它们仍然是函数),因此它更加一致,并且可以节省您的工作

例如,这是它的工作方式:

本示例将使用的包括:

#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
于 2011-10-07T06:39:05.267 回答
7

C++03 中没有可变参数模板功能。Boost 和其他设计良好的库以不同的方式解决这个问题。对于函数,可以有许多 N + 1 个重载,其中每个重载采用 0 到 N 个参数。对于类,一个定义最多可以有 N 个参数,这些参数默认为某种无效类型。这个更高的限制通常可以通过一些宏来配置;因为将其设置为高会增加编译时间,而将其设置为低会导致用户无法传递足够的参数。

对于您的特定情况,我将以VarPrint递归方式实现。递归中的每一步都将处理一个参数,并使用修改后的格式字符串发出递归调用,并将所有左侧值向左移动一个位置。

于 2011-10-07T04:43:58.803 回答