6

このプロジェクトでは、printf 互換関数を使用して、外部ログ ファイルにメッセージを追加します。例えば、私たちは書くことができます

__LOG_INFO( "number of files = %d\n", number_of_files );
__LOG_INFO( "Just for information\n" );

の関数宣言は次の__LOG_INFOようになります

template<int N>
inline void __LOG_INFO( const char (&fmt)[N] )
{
    call_printf( MODULE_NAME, fmt, debug_parameters() );
}

template<int N, typename T1>
static void __LOG_INFO( const char (&fmt)[N], const T1 &t1 )
{
    call_printf( MODULE_NAME, fmt, debug_parameters( t1 ) );
}

template<int N, typename T1, typename T2>
static void __LOG_INFO( const char (&fmt)[N], const T1 &t1, const T2 &t2 )
{
    call_printf( MODULE_NAME, fmt, debug_parameters( t1, t2 ) );
}

...

ここで、C++ 11 constexpr 機能を使用してコンパイル時のフォーマット文字列チェックをいくつか追加したいと考えています。たとえば、フォーマット文字列のパラメーター数を非常に簡単にチェックするには、この関数を使用します。

template<int N>
constexpr static int count_arguments( const char (&fmt)[N], int pos = 0, int num_arguments = 0 )
{
    return pos >= N-2 ? num_arguments :
                        fmt[pos] == '%' && fmt[pos+1] != '%' ? count_arguments( fmt, pos+1, num_arguments+1 ) :
                                                               count_arguments( fmt, pos+1, num_arguments );
}

__LOG_INFOここでの問題は、コンパイラが fmt が整数定数ではないと文句を言うため、関数自体の内部に static_assert のようなものを追加できないことです。だから今、私たちはこの醜いマクロソリューションを持っています:

#define COUNT_ARGS(...) COUNT_ARGS_(,##__VA_ARGS__,8,7,6,5,4,3,2,1,0)
#define COUNT_ARGS_(z,a,b,c,d,e,f,g,h,cnt,...) cnt

#define LOG_INFO(a, ...) \
  { \
      static_assert( count_arguments(a)==COUNT_ARGS(__VA_ARGS__), "wrong number of arguments in format string" ); \
      __LOG_INFO(a,##__VA_ARGS__); \
  }

したがって、 を呼び出す代わりに、 を呼び出す__LOG_INFO必要がありますLOG_INFO

上記のマクロを使用する以外に、より良い解決策はありますか?

4

1 に答える 1

7

私はコンパイル時のフォーマット文字列ライブラリに取り組んでおり、その間に同様の問題に遭遇しました。そこで、ここで私の発見を共有します。

主な問題は、constexpr関数が C++ で定義されており、コンパイル時と実行時に呼び出し可能であることです。F次の例は、ランタイム コンテキストから呼び出すことができる必要があるため、無効です。

/* Invalid constexpr function */
constexpr int F(int x) { static_assert(x == 42, ""); return x; }

/* Compile-time context */
static_assert(F(42) == 42, "");

/* Runtime context */
void G(int y) { F(y); }  // The way F is defined, this can't ever be valid.

パラメーターの型がテンプレート パラメーターとして許可されているものの、解決策が単純な場合は、テンプレート パラメーターを介して渡すだけです。しかし、そうでない場合はconstexpr、ラムダを使用して任意のスコープ内のクラスで式の -ness をラップできます。

constexprパラメーター

/* Simply counts the number of xs, using C++14. */
static constexpr std::size_t count_xs(const char *fmt, std::size_t n) {
  std::size_t result = 0;
  for (std::size_t i = 0; i < n; ++i) {
    if (fmt[i] == 'x') {
      ++result;
    }  // if
  }  // for
  return result;
}

template <typename StrLiteral, typename... Args>
constexpr void F(StrLiteral fmt, Args &&... args) {
  static_assert(count_xs(fmt, fmt.size()) == sizeof...(Args), "");
}

int main() {
  F([]() {
      class StrLiteral {
        private:
        static constexpr decltype(auto) get() { return "x:x:x"; }
        public:
        static constexpr const char *data() { return get(); }
        static constexpr std::size_t size() { return sizeof(get()) - 1; }
        constexpr operator const char *() { return data(); }
      };
      return StrLiteral();
    }(), 0, 0, 0);
}

コールサイトはばかげています。私はマクロが嫌いですが、マクロを使って少しクリーンアップすることができます。

#define STR_LITERAL(str_literal) \
  []() { \
    class StrLiteral { \
      private: \
      static constexpr decltype(auto) get() { return str_literal; } \
      public: \
      static constexpr const char *data() { return get(); } \
      static constexpr std::size_t size() { return sizeof(get()) - 1; } \
      constexpr operator const char *() { return data(); } \
    }; \
    return StrLiteral(); \
  }()

int main() {
  F(STR_LITERAL("x:x:x"), 0, 0, 0);
}

constexpr一般に、式を関数でラップするこの手法を使用して、関数パラメーターを通じてstatic constexprその式を保持できます。constexprただしF、同等の文字列で 2 回呼び出しても、 を呼び出すたびに異なるテンプレートのインスタンス化が発生するため、これによりコンパイル時間が失われる可能性があることに注意してください。

わずかな改善

への呼び出しごとに異なるテンプレートをインスタンス化するのではなくF、同じフォーマット文字列に対して同じインスタンス化を再利用するようにすることができます。

template <char... Cs>
class Format {
  private:
  static constexpr const char data_[] = {Cs..., '\0'};
  public:
  static constexpr const char *data() { return data_; }
  static constexpr std::size_t size() { return sizeof(data_) - 1; }
  constexpr operator const char *() { return data(); }
};

template <char... Cs>
constexpr const char Format<Cs...>::data_[];

template <char... Cs, typename... Args>
constexpr void F(Format<Cs...> fmt, Args &&... args) {
  static_assert(count_xs(fmt, fmt.size()) == sizeof...(Args), "");
}

int main() {
  F(Format<'x', ':', 'x', ':', 'x'>(), 0, 0, 0);
}

Formatうーん、別のマクロを使用して、構造を「より良く」しましょう。

template <typename StrLiteral, std::size_t... Is>
constexpr auto MakeFormat(StrLiteral str_literal,
                          std::index_sequence<Is...>) {
  return Format<str_literal[Is]...>();
}

#define FMT(fmt) \
   MakeFormat(STR_LITERAL(fmt), std::make_index_sequence<sizeof(fmt) - 1>())

int main() {
  F(FMT("x:x:x"), 0, 0, 0);
}

お役に立てれば!

于 2014-08-22T01:08:14.903 に答える