2011-12-09 14 views
4

私はクラステンプレートと2つの特殊変数、と_2のセットを実装します。彼らは、以下の法的なコードにする必要がありますSyntatic sugar:シンプルな関数オブジェクトを自動的に作成する

// Sort ascending 
std::sort(a, a+5, _1 > _2); 

// Output to a stream 
std::for_each(a, a+5, std::cout << _1 << " "); 

// Assign 100 to each element 
std::for_each(a, a+5, _1 = 100); 

// Print elements increased by five 5 
std::transform(a, a+5, std::ostream_iterator<int>(std::cout, " "), _1 + 5); 

私は_1 * 5も単項機能をもたらすだけでなく、_1/5べきであると仮定など

  • ませブーストありません許可
  • ませラムダは
を許しません3210

今私はが非常にで、テンプレートとテンプレートのメタプログラミングに関する経験が少ないので、どこから始めたらいいのか、そして私のクラステンプレートの構造はどうなっているのだろう。私は私のクラステンプレートの中に、これらすべての実装を記述する必要があるかどうか分からないので、特に混乱しています。operator=operator>>operator+...-...*.../ - またはもっと一般的な方法があります。

これらの演算子の実装例についての回答に特に感謝します。テンプレートはまだ私にとって大きな混乱のように見えます。

ありがとうございます!

+0

Boost.Lambda ... – kennytm

+2

@KennyTM:それがどのように機能するかを学ぶ理由。 –

+0

テンプレートが必要な理由はわかりませんが、使用しない - 、*、/以外のすべての演算子をオーバーロードする必要があります。 –

答えて

2

簡単な例:

template <typename T> 
class Parameter 
{ 
}; 

template <typename T> 
struct Ascending 
{ 
    bool operator()(T left, T right) 
    { 
     return left < right; 
    } 
}; 

template <typename T> 
Ascending<T> operator > (Parameter<T> p1, Parameter<T> p2) 
{ 
    return Ascending<T>(); 
} 

int main() 
{ 

    std::vector<int> vec; 
    vec.push_back(3); 
    vec.push_back(6); 
    vec.push_back(7); 
    vec.push_back(2); 
    vec.push_back(7); 

    std::vector<int>::iterator a = vec.begin(); 

    Parameter<int> _1; 
    Parameter<int> _2; 

    std::sort(a, a+4, _1 > _2); 
} 
5

まあ!それは本当に難しい宿題です。しかし、それはまた、働き、学ぶことは非常に良い問題です。

私はこれに答える最善の方法は、単純なユースケースから始め、段階的にソリューションを構築することだと思います。例えば

、あなたはで動作するようにstd::vector<int>を、次のしていることとします

std::vector<int> vec; 
vec.push_back(4); 
vec.push_back(-8); 
vec.push_back(1); 
vec.push_back(0); 
vec.push_back(7); 

あなたは明らか以下のユースケース許可することをお勧めします:

std::for_each(vec.cbegin(), vec.cend(), _1); 

をしかし、これを許可する方法?まず、_1を定義する必要があります。次に、_1の種類の関数呼び出し演算子の「何か」のオーバーロードを実装する必要があります。

Boost LambdaとBoost Bindでプレースホルダオブジェクト_1_2を定義する方法は、ダミータイプにすることです。例えば、_1オブジェクトは、タイプplaceholder1_tを持っているかもしれません:「ダミーのタイプは」頻繁に、非公式にタグタイプと呼ばれているこのような

struct placeholder1_t { }; 
placeholder1_t _1; 

struct placeholder2_t { }; 
placeholder2_t _2; 

。多くのC++ライブラリ、実際にはタグタイプに依存するSTL(例:std::nothrow_t)があります。これらは、実行するために「正しい」関数のオーバーロードを選択するために使用されます。本質的に、ダミーオブジェクトはタグタイプを持って作成され、これらは関数に渡されます。関数は何らかの方法でダミーオブジェクトを使用しません(実際にはほとんどの場合、パラメータ名が指定されていません)が、その余分なパラメータが存在するため、コンパイラは適切なオーバーロードを呼び出して呼び出すことができます。

placeholder1_tの定義を、関数呼び出し演算子のオーバーロードを追加して拡張しましょう。我々はそれが何かを受け入れるようにしたいということを忘れないでください、その関数呼び出し演算子のオーバーロードは、自分自身を引数の型にテンプレート化されます。

struct placeholder1_t 
{ 
    template <typename ArgT> 
    ArgT& operator()(ArgT& arg) const { 
     return arg; 
    } 

    template <typename ArgT> 
    const ArgT& operator()(const ArgT& arg) const { 
     return arg; 
    } 
}; 

それです!最も簡単なユースケースはコンパイルされて実行されます。

std::for_each(vec.cbegin(), vec.cend(), _1); 

もちろん、基本的にはノーオペレーションになります。

_1 + 5で作業しましょう。その式は何ですか。ですか?それは、(何らかの不明な型の)引数で呼び出されたときにその引数に5を加えた単項関数オブジェクトを返さなければなりません。これをより汎用的にすると、式は単体機能オブジェクト+オブジェクトです。返されるオブジェクトは、単体の機能オブジェクトです。

返されるオブジェクトの型を定義する必要があります。単項機能タイプと単項機能の結果に追加されるオブジェクトのタイプ:それは、2つのテンプレートの型パラメータを持つテンプレートであろう

template <typename UnaryFnT, typename ObjT> 
struct unary_plus_object_partfn_t; 

「partfn」部分を表す機能的なタイプを指しバイナリ+オペレータのアプリケーション。

template <typename UnaryFnT, typename ObjT> 
struct unary_plus_object_partfn_t 
{ 
    UnaryFnT m_fn; 
    ObjT m_obj; 

    unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj) 
     : m_fn(fn), m_obj(obj) 
    { 
    } 
}; 

さて:このタイプのインスタンスは、単項機能オブジェクト(有するタイプUnaryFnT)および他のオブジェクト(タイプObjTを有する)のコピーを必要とします。関数呼び出し演算子はまた、任意の引数を許可するためにオーバーロードする必要があります。我々は、それが事前にあるのかわからないと式の型を参照するためにC++ 11 decltype機能を使用します:

template <typename UnaryFnT, typename ObjT> 
struct unary_plus_object_partfn_t 
{ 
    UnaryFnT m_fn; 
    ObjT m_obj; 

    unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj) 
     : m_fn(fn), m_obj(obj) 
    { 
    } 

    template <typename ArgT> 
    auto operator()(ArgT& arg) const -> decltype(m_fn(arg) + m_obj) { 
     return m_fn(arg) + m_obj; 
    } 

    template <typename ArgT> 
    auto operator()(const ArgT& arg) const -> decltype(m_fn(arg) + m_obj) { 
     return m_fn(arg) + m_obj; 
    } 
}; 

複雑に取得し始めていますが、何の驚きは、この中ではありませんコード。本質的には、関数呼び出し演算子は、実際にはあらゆる引数を受け入れるためにオーバーロードされていると言います。次に、引数にm_fn(単項関数オブジェクト)を呼び出し、結果にm_objを追加します。戻り値の型はm_fn(arg) + m_objの宣言型です。

さて型が定義されていることを、我々は左のタイプplaceholder1_tのオブジェクトを受け入れるバイナリ演算子+の過負荷を書くことができます

template <typename ObjT> 
inline unary_plus_object_partfn_t<placeholder1_t, ObjT> operator+(const placeholder1_t& fn, ObjT obj) 
{ 
    return unary_plus_object_partfn_t<placeholder1_t, ObjT>(fn, obj); 
} 

現在第2の使用ケースをコンパイルして実行することができます

std::transform(vec.cbegin(), vec.cend(), std::ostream_iterator<int>(std::cout, " "), _1 + 5); 
std::cout << std::endl; 

出力:

 
9 -3 6 5 12 
を3210

これは基本的に問題を解決するために必要なすべてです。カスタム関数型を書く方法を考えてください。そのインスタンスは演算子のオーバーロードによって返される可能性があります。

EDIT:参照渡しを使用して関数呼び出し演算子のオーバーロードを改善しました。

EDIT2:場合によっては、オブジェクトのコピーではなくオブジェクトへの参照を格納する必要があります。たとえば、std::cout << _1を収容するには、std::ios_baseコピーコンストラクタがプライベートであり、std::ostreamを含むstd::ios_baseから派生した任意のクラスのコンストラクトオブジェクトをコピーすることができないため、結果の機能オブジェクトにstd::coutへの参照を格納する必要があります。

std::cout << _1を許可するには、ref_insert_unary_partfn_tテンプレートを作成すると便利です。そのようなテンプレートは、上記unary_plus_object_partfn_tの例のように、オブジェクトの種類と単項機能タイプにテンプレートされるであろう:このテンプレートのインスタンス化の

template <typename ObjT, typename UnaryFnT> 
struct ref_insert_unary_partfn_t; 

インスタンスとしてタイプObjTのオブジェクトへの参照を格納する必要がありますほかのタイプUnaryFnTの単項機能オブジェクトのコピーとして:

template <typename ObjT, typename UnaryFnT> 
struct ref_insert_unary_partfn_t 
{ 
    ObjT& m_ref; 
    UnaryFnT m_fn; 

    ref_insert_unary_partfn_t(ObjT& ref, UnaryFnT fn) 
     : m_ref(ref), m_fn(fn) 
    { 
    } 
}; 

挿入演算子、<<のと同様に過負荷前と関数呼び出し演算子のオーバーロードを追加します。

std::cout << _1の場合、返されるオブジェクトのタイプはref_insert_unary_partfn_t<std::basic_ostream<char>, placeholder1_t>です。

+0

このような詳細な対応をありがとうございます! decltypeのようなC++ 11の機能を全く使用できない場合、私のコードにはどのような制限がありますか? – wh1t3cat1k

+0

@ wh1t3cat1k:これはすべて技術的にはC++ 03のみで実現可能です。結局、Boost Lambdaは非C++ 11コンパイラで動作します。しかし、私はBoost Lambdaが 'decltype'演算子の欠如を回避する方法についてはわかりません。 –

関連する問題