7

Variadic テンプレートを使用すると、特定の種類の関数をよりクリーンでタイプ セーフなバージョンに書き換えることができます。ウィキペディアprintfにある例のように、の場合です。

void printf(const char *s)
{
    while (*s) {
        if (*s == '%' && *(++s) != '%')
            throw std::runtime_error("invalid format string: missing arguments");
        std::cout << *s++;
    }
}

template<typename T, typename... Args>
void printf(const char *s, T value, Args... args)
{
    while (*s) {
        if (*s == '%' && *(++s) != '%') {
            std::cout << value;
            ++s;
            printf(s, args...); // call even when *s == 0 to detect extra arguments
            return;
        }
        std::cout << *s++;
    }
    throw std::logic_error("extra arguments provided to printf");
}

しかし...私がテンプレートを理解する限り、それらは型の組み合わせごとにコードの重複を意味します。したがって、上記printfの s の可変長バージョンは何度もコピーされます。これは、大規模な関数やクラスの場合はひどいものになる可能性があります。

可変個引数テンプレートは、コード複製の標準テンプレートと同じくらい危険ですか? はいの場合、継承のトリックはまだ役に立ちますか?

4

2 に答える 2

11

簡単に言えば、「使用した分だけ支払う」という原則は、以前とまったく同じように適用されます。

より長い答えは、2 つの仮想実装で生成されたコードを比較することで確認できます。

#include <iostream>

template <typename T>
void func1(T& v) {
  v = -10;
}

template <typename T1, typename T2>
void func1(T1& v1, T2& v2) {
  func1(v1); func1(v2);
}

// More unused overloads....
template <typename T1, typename T2, typename T3>
void func1(T1& v1, T2& v2, T3& v3) {
  func1(v1); func1(v2); func1(v3);
}

int main() {
  double d;
  int i;
  func1(d);
  func1(i);
  std::cout << "i=" << i << ", d=" << d << std::endl;
  func1(d,i);
  std::cout << "i=" << i << ", d=" << d << std::endl;
}

最新のコンパイラを使用すると、テンプレートをすべてまとめて回避したい場合に、これを正確に記述できるようになります。この「従来の」C++03 テンプレート コードでは、私のバージョンの g++ インライン (キーワードの意味ではなく、コンパイラで) 全体がインライン化されており、初期化がテンプレート関数の参照を介して複数回、異なる方法で行われているという明確なヒントはありません。方法。

同等の可変個引数アプローチと比較すると、次のようになります。

#include <iostream>
#include <functional>

void func1() {
  // end recursion
}

template <typename T, typename ...Args>
void func1(T& v, Args&... args) {
  v = -10;
  func1(args...);
}

int main() {
  double d;
  int i;
  func1(d);
  func1(i);
  std::cout << "i=" << i << ", d=" << d << std::endl;
  func1(d,i);
  std::cout << "i=" << i << ", d=" << d << std::endl;
}

g++ -Wall -Wextra -Sこれもほとんど同じコードを生成します - 予想通りラベルとマングルされた名前の一部が異なりますが、 (4.7 スナップショット)によって生成された生成された asm の diff には大きな違いはありません。コンパイラは基本的に、プログラムが必要とするすべてのオーバーロードをオンザフライで記述し、以前と同様に最適化します。

次の非テンプレート コードも、ほぼ同じ出力を生成します。

#include <iostream>
#include <functional>

int main() {
  double d;
  int i;
  d= -10; i=-10;
  std::cout << "i=" << i << ", d=" << d << std::endl;
  d= -10; i=-10;
  std::cout << "i=" << i << ", d=" << d << std::endl;
}

ここでも、顕著な違いはラベルとシンボル名だけです。

要点は、最新のコンパイラは、テンプレート コードであまり手間をかけずに「正しいこと」を実行できるということです。すべてのテンプレート メカニクスの下で表現しているものがシンプルであれば、出力はシンプルになります。そうでない場合、出力はより実質的になりますが、テンプレートを完全に回避した場合の出力も同様になります。

ただし、これが興味深いのは(私の見解では)これです。私のステートメントはすべて、「適切な最新のコンパイラを使用して」のようなもので修飾されていました。可変個引数テンプレートを作成している場合、コンパイルに使用しているものまともな最新のコンパイラであることはほぼ確実です。不格好な古い遺物のコンパイラは、可変個引数テンプレートをサポートしていません。

于 2011-11-10T15:01:16.360 に答える
6

それは確かに問題になる可能性があります。役立つことの 1 つは、共通部分を除外することです。

const char *process(const char *s)
{
  while (*s) {
      if (*s == '%' && *(++s) != '%') {
          ++s;
          return s;
      }
      std::cout << *s++;
  }
  throw std::logic_error("extra arguments provided to printf");
}

template<typename T>
inline const char *process(const char *s,T value)
{
  s = process(s);
  std::cout << value;
  return s;
}

template<typename T, typename... Args>
inline void printf(const char *s, T value, Args... args)
{
  printf(process(s,value),args...);
}
于 2011-11-10T15:12:31.270 に答える