web-dev-qa-db-ja.com

C ++のテンプレートの前に人々は何をしましたか?

私はプログラミングに不慣れではありませんが、数年前に始まったもので、テンプレートが大好きです。

しかし、以前は、テンプレートのようなコンパイル時のコード生成が必要な状況に人々はどのように対処しましたか?私は恐ろしくて恐ろしいマクロを推測しています(少なくともそれは私がやる方法です)、上の質問をググってみると、ページとテンプレートチュートリアルのページしか表示されません。

テンプレートの使用に対する多くの議論があり、それは通常読みやすさに要約されますが、 " [〜#〜] yagni [〜#〜] "、そしてそれがどれほど不十分に実装されているかについて不平を言いますが、同様の力を持つ代替案についてはたくさんあります。 doが何らかのコンパイル時ジェネリックを実行する必要がある場合、およびdoコードを保持したい [〜#〜] dry [〜#〜] 、テンプレートの使用をどのように/回避しましたか?

44
IdeaHat

カバーされるvoid *ポインターの他に Robertの回答 のようなテクニックが使用されました(免責事項:20年前のメモリ):

#define WANTIMP

#define TYPE int
#include "collection.h"
#undef TYPE

#define TYPE string
#include "collection.h"
#undef TYPE

int main() {
    Collection_int lstInt;
    Collection_string lstString;
}

Collection.h内の正確なプリプロセッサマジックを忘れたところですが、次のようなものでした。

class Collection_ ## TYPE {
public:
 Collection() {}
 void Add(TYPE value);
private:
 TYPE *list;
 size_t n;
 size_t a;
}

#ifdef WANTIMP
void Collection_ ## TYPE ::Add(TYPE value)
#endif
52
Joshua

ジェネリック(テンプレートが作成された理由)なしでジェネリックを実装する従来の方法は、voidポインターを使用することです。

typedef struct Item{ 
        void* data;
    } Item;

typedef struct Node{
    Item Item; 
    struct Node* next; 
    struct Node* previous;
} Node;

このサンプルコードでは、バイナリツリーまたは二重リンクリストを表すことができます。 itemはvoidポインターをカプセル化しているため、そこに任意のデータ型を格納できます。もちろん、実行時にデータ型を知って、使用可能なオブジェクトにキャストできるようにする必要があります。

46
Robert Harvey

他の回答が指摘したように、void*汎用データ構造。他の種類のパラメトリック多態性については、lotが繰り返された場合(数十回など)、プリプロセッサマクロが使用されました。ただし、正直に言うと、ほとんどの場合、適度な繰り返しでは、コピーして貼り付けてから型を変更するだけです。マクロを使用すると、問題を引き起こす多くの落とし穴があるためです。

blub paradox の逆の名前が本当に必要です。このサイトでは多くの場合、あまり表現力のない言語でのプログラミングを想像するのに苦労しています。パラメトリックポリモーフィズムを実装する表現力豊かな方法で言語を使用したことがない場合、何が欠けているのか本当にわかりません。コピーと貼り付けをやや煩わしいものとして受け入れますが、必要です。

現在選択している言語には、まだ気付いていない非効率な点があります。 20年後、人々はあなたがそれらをどのように排除したのか疑問に思うでしょう。簡単な答えは、あなたができることを知らなかったからです。

18
Karl Bielefeldt

Gccがgenclassとともに出荷されたときのことを覚えています-入力としてパラメータタイプのセット(たとえば、マップのキーと値)を受け取り、パラメータ化されたタイプ(たとえば、マップまたはVector)と入力し、param型が入力された有効なC++実装を生成しました。

したがって、必要な場合はMap<int, string>およびMap<string, string>(これは実際の構文ではありませんでした。注意してください)map_string_string.hやmap_int_string.hのようなものを生成するには、そのプログラムを2回実行して、コードで使用する必要がありました。

これがgenclassのmanページです: http://manpages.ubuntu.com/manpages/dapper/man1/genclass.1.html

9
Rafał Dowgird

[OPへ:私は個人的にあなたを選ぶつもりはありませんが、SEや他の場所で尋ねられた質問の論理について考えるあなたや他の人々の意識を高めます。これを個人的に服用しないでください!]

質問のタイトルは適切ですが、「...コンパイル時のコード生成が必要な状況」を含めることで、回答の範囲を厳しく制限しています。このページには、テンプレートを使用せずにC++でコンパイル時にコードを生成する方法に関する質問に対する多くの良い回答がありますが、最初に提起した質問に回答する必要があります。

C++のテンプレートの前に人々は何をしましたか?

答えは、もちろん、彼ら(私たち)はそれらを使用しなかったということです。はい、私は口の利き者ですが、本文の質問の詳細は、(おそらく誇張して)誰もがテンプレートを愛し、テンプレートなしではコーディングを実行できなかったことを想定しているようです。

例として、コンパイル時のコード生成を必要とせずに、さまざまな言語で多くのコーディングプロジェクトを完了しました。確かに、テンプレートによって解決された問題は、誰かが実際にそれを引っ掻くほどのかゆみでしたが、この質問によって提示されたシナリオは、ほとんど存在していませんでした。

車で同様の質問を考えてみましょう:

自動変速装置が発明される前に、ドライバーはギアをシフトする自動化された方法を使用して、ギアを別のギアにどのようにシフトしましたか?

問題は、もちろん、ばかげています。 Xが発明される前に人がどのようにXをしたかを尋ねることは、実際には有効な質問ではありません。答えは一般的に、「私たちはそれが存在することを知らなかったため、私たちはそれを実行せず、見逃しませんでした」です。はい、実際に利点を確認するのは簡単ですが、誰もが立って、かかとを蹴り、自動送信またはC++テンプレートを待っていると想定することは、実際には当てはまりません。

「自動変速機が発明される前に、ドライバーはどのようにギアをシフトしましたか?」 「手動で」合理的に答えることができ、それがあなたがここで得ている答えのタイプです。それはあなたが尋ねるつもりだった種類の質問かもしれません。

しかし、それはあなたが尋ねたものではありませんでした。

そう:

Q:テンプレートが発明される前に、人々はどのようにテンプレートを使用しましたか?

A:ありませんでした。

Q:彼らはテンプレートを使用する必要があったであるとき、人々はテンプレートが発明される前にテンプレートをどのように使用しましたか?

A:私たちはしませんでしたそれらを使用する必要があります。なぜ私たちがそうしたと思いますか? (なぜそうするのか?)

Q:テンプレートが提供する結果を達成するための代替方法は何ですか?

A:上記には多くの良い答えがあります。

投稿する前に、投稿の論理的な誤りについて考えてください。

[ありがとうございました!ここに害はありません。]

7
Joseph Cheek

http://www.artima.com/intv/modern2.html からの恐ろしいマクロは正しいです:

Bjarne Stroustrup:はい。 「テンプレートタイプT」と言うと、「すべてのTに対して」という古い数学です。それが考えられている方法です。 1981年の「C with Classes」(C++に進化した)に関する私の最初の論文では、パラメーター化された型について言及しました。そこで、私は問題を正解しましたが、解決策は完全に間違っていました。マクロで型をパラメーター化する方法と、お粗末なコードである男の子について説明しました。

テンプレートの古いマクロバージョンがどのように使用されたかを確認できます: http://www.xvt.com/sites/default/files/docs/Pwr++_Reference/rw/docs/html/toolsref/ rwgvector.html

6
jas

Robert Harveyがすでに述べたように、voidポインターは一般的なデータ型です。

標準Cライブラリの例、一般的な並べ替えでdoubleの配列を並べ替える方法:

double *array = ...;
int size = ...;   
qsort (array, size, sizeof (double), compare_doubles);

どこ compare_double と定義されている:

int compare_doubles (const void *a, const void *b)
{
    const double *da = (const double *) a;
    const double *db = (const double *) b;
    return (*da > *db) - (*da < *db);
}

qsortの署名はstdlib.hで定義されています:

void qsort(void *base, size_t nmemb, size_t size,
    int (*compar)(const void *, const void *)
);

コンパイル時ではなく、実行時でも型チェックがないことに注意してください。上記のコンパレータを使用して文字列のリストをdoubleを期待してソートする場合、文字列のバイナリ表現をdoubleとして解釈し、それに応じてソートしようとします。

5
Florian F

これを行う1つの方法は次のとおりです。

https://github.com/rgeminas/gp--/blob/master/src/scope/darray.h

#define DARRAY_DEFINE(name, type) DARRAY_TYPEDECL(name, type) DARRAY_IMPL(name, type)

// This is one single long line
#define DARRAY_TYPEDECL(name, type) \
typedef struct darray_##name \
{ \
    type* base; \
    size_t allocated_mem; \
    size_t length; \
} darray_##name; 

// This is also a single line
#define DARRAY_IMPL(name, type) \
static darray_##name* darray_init_##name() \
{ \
    darray_##name* arr = (darray_##name*) malloc(sizeof(darray_##name)); \
    arr->base = (type*) malloc(sizeof(type)); \
    arr->length = 0; \
    arr->allocated_mem = 1; \
    return arr; \
}

DARRAY_TYPEDECLマクロは、構造体定義を(1行で)効果的に作成し、nameを渡した名前に置き換え、渡したtypeの配列(name DARRAY_IMPLマクロがその構造体を操作する関数を定義している場合(その場合、それらは、構造体名に連結しても有効な識別子-darray_int *は構造体の有効な名前ではありません)定義を1回だけ呼び出し、すべてを分離しないように、静的に再度マークしました)。

これは次のように使用されます。

#include "darray.h"
// No types have been defined yet
DARRAY_DEFINE(int_ptr, int*)

// by this point, the type has been declared and its functions defined
darray_int_ptr* darray = darray_int_ptr_init();
1
Renan Gemignani

テンプレートは、動的配列(ベクトル)、マップ、ツリーなどのアルゴリズム値が多いコンテナタイプを再利用する方法としてよく使用されていると思います。

テンプレートがなければ、必然的に、これらの実装は一般的な方法で記述され、ドメインに必要なタイプに関する十分な情報が提供されます。たとえば、ベクターの場合、データが必要であり、各アイテムのサイズを知っている必要があります。

これを行うVectorと呼ばれるコンテナークラスがあるとします。 void *が必要です。これの単純な使用法は、アプリケーション層コードが多くのキャストを行うことです。したがって、Catオブジェクトを管理している場合、Cat *をvoid *にキャストし、すべての場所に戻る必要があります。キャストでアプリケーションコードをポイ捨てすると、明らかな問題が発生します。

テンプレートはこれを解決します。

それを解決する別の方法は、コンテナーに格納している型のカスタムコンテナー型を作成することです。したがって、Catクラスがある場合は、Vectorから派生したCatListクラスを作成します。次に、使用するいくつかのメソッドをオーバーロードし、void *の代わりにCatオブジェクトを使用するバージョンを導入します。そのため、Vector :: Add(void *)メソッドをCat :: Add(Cat *)でオーバーロードします。これは内部的にパラメーターをVector :: Add()に渡すだけです。次に、アプリケーションコードで、Catオブジェクトを渡すときにオーバーロードされたバージョンのAddを呼び出し、キャストを回避します。公平に言えば、Cat *オブジェクトはキャストなしでvoid *に変換されるため、Addメソッドはキャストを必要としません。ただし、インデックスオーバーロードやGet()メソッドなど、アイテムを取得するメソッドでは可能です。

もう1つのアプローチは、90年代前半から大規模なアプリケーションフレームワークを使用して思い出した唯一の例ですが、クラス上にこれらの型を作成するカスタムユーティリティを使用することです。 MFCはこれを行ったと思います。 CStringArray、CRectArray、CDoulbeArray、CIntArrayなどのコンテナー用に異なるクラスがありました。重複したコードを維持するのではなく、クラスを生成する外部ツールを使用して、マクロに似たある種のメタプログラミングを行いました。彼らは、誰もがそれを使用したいと思った場合に備えて、ツールをVisual C++で利用できるようにしました-私はしませんでした。多分私は持っているべきです。しかし、当時、専門家たちは「C++の健全なサブセット」と「テンプレートは必要ない」と宣伝していました。

1
zumalifeguard