10

Visual Studio 2008を使用していますが、変数引数リストなしで文字列フォーマット関数を実装したいと思います。

pre-c ++ 0x(VS2008)で「可変個引数テンプレート」を実装するにはどうすればよいですか?

ブーストのようにこれを実装するライブラリはありますか?

またはこれを実装する別の方法はありますか?

これが私のサンプルコードです。(もちろん、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(1)("some string")(foo)
  3. オブジェクトを使用し、いくつかの演算子をオーバーロードします(operator()たとえば、operator%Boost.Formatなど)

最初のオプションは少し注意が必要です。誰もがマクロを簡単に理解できるわけではないので、すぐにC ++ 0xに移行する予定がある場合は、短期的なソリューションのためだけに予約します。

3番目のオプションは素晴らしいカスタムタッチを提供するかもしれません(フォーマットは%多くの言語のサインで行われます)が、それはまた、この特定の「可変個引数」関数が毎回どのように機能するかを覚えておく必要があることを意味します。

私の個人的な好みは、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やその他の適切に設計されたライブラリは、さまざまな方法でこれを回避します。関数の場合、各オーバーロードが0からNの引数を取る、N+1個のオーバーロードを持つことができます。クラスの場合、デフォルトで無効な型になる最大N個の引数を持つ単一の定義を持つことができます。この上限は通常、いくつかのマクロを介して構成できます。これは、高く設定するとコンパイル時にオーバーヘッドが発生し、低く設定するとユーザーが十分な引数を渡すことができなくなるためです。

あなたの特定のケースでVarPrintは、再帰的な方法で実装します。再帰の各ステップは単一の引数を処理し、変更されたフォーマット文字列とすべての左の値を1つ左にシフトして再帰呼び出しを発行します。

于 2011-10-07T04:43:58.803 に答える