2016-11-09 7 views
0

私は基本クラスへの2つのポインタを持っています.1つは実際の基本オブジェクトを指し、もう1つは派生オブジェクトを指しています。私はまた、ベースクラスと派生クラスのためにオーバーロードされている非メンバ関数を持っています。私は正しいオーバーロードが呼び出されるようにポインタをダウンキャストする多態性を使用したいと思います。多型でダウンキャストしようとしていますが、どうなっていますか?

コードの出力は

base downcast called 
base 
derived downcast called 
base 

しかし、所望の出力は

base 
derived 

でいる人には、出力を説明することができ、そして所望の動作を取得するために何をしなければなりませんか?

#include <iostream> 
using namespace std; 

class base 
{ 
public: 
    virtual base& downcast() 
    { 
    cout << "base downcast called" << endl; 
    return *this; 
    } 
}; 

class derived: public base 
{ 
public: 
    virtual derived& downcast() 
    { 
    cout << "derived downcast called" << endl; 
    return *this; 
    } 
}; 

void foo(const base& a) 
{ 
    cout << "base" << endl; 
} 

void foo(const derived& a) 
{ 
    cout << "derived" << endl; 
} 


int main() 
{ 
    base* ptr1 = new(base); 
    base* ptr2 = new(derived); 

    foo(ptr1->downcast()); 
    foo(ptr2->downcast()); 

    return 0; 
} 

EDIT:関数オーバーライド/多型を説明するためにダウンキャスト関数にcoutを追加しました。

+0

@tkausl - ベースのバージョンをオーバーライドする必要があります。 OPの問題は、オーバーライドするのではなく、隠れている、オーバーロードしているということではありませんが、コンパイラはベースのバージョンの関数とその戻り値の型を見ています。 –

+0

@tkauslはい、それはそれを無効にします。あなたのデモは、[ここ](https://ideone.com/jSqb17)です。実際の理由は、関数呼び出しは実行時ではなくコンパイル時に解決されるということです。 'ptr2'は' base * '型で' base :: downcast'は 'base&'を返すので、 'void foo(const base&a)'が呼び出されます。 –

+0

動作しません。 'foo'を仮想メンバ関数にするか、Visitorパターンを使いたいかもしれません。 –

答えて

1

dynamic_castが必要です。あなたがしようとしていることは動作しません。

C++は、での共変の戻り値を無効にします。関数です。 derivedbaseから継承しているため、この資格があります。しかし、関数の基底のバージョンを呼び出すと、派生したクラスのバージョンではなく、ITが返す型が得られます。その戻り値の型を取得するには、コンパイラが関数が返す型が分かるように、すでにキャストしておく必要があります。

+0

実際には 'boost :: variant'で動作するかもしれませんが、それは別の話です – Slava

+0

"あなたはdynamic_castが必要です "とはどういう意味でしょうか?作業コードがありますか? –

+0

@ n.m。 'if(auto pD = dynamic_cast (ptr2))foo(pD)のようなものです。 else foo(ptr2); '。 (しかし、私はそれを1ライナーとして書いていません) –

4

基本的に、コンパイル時のオーバーロード解決に影響を与える実行時の多型を作成しようとしています。明らかな理由から、これは不可能です。関数のオーバーロードはコンパイル時の機能です。つまり、のstatic型の関数引数に基づいて、コンパイル時にオーバーロードの解決が実行されます。

あなたのケースで呼び出すfooの選択は静的 typizationに基づいています:ptr1ptr2静的種類に及びptr1->downcast()ptr2->downcast()戻り値の静的タイプに。後者は両方の場合にタイプbaseの左辺値(参照)です。 fooの選択には多型性はありません。コンパイラは、実行時にこれらのbase &参照のうちの1つが実際にderivedオブジェクトを参照することを知りません(気にしません)。

a.downcast()fooから呼び出すことができれば、ラムタイム多型はまだfooの内部で動作することがわかります。 (現時点では、downcast()が非constであるため、そのような呼び出しは不可能です)

+0

派生したダウンキャスト関数が呼び出されると多態性がありますが、コンパイル時のオーバーロードの解決については何を言っているのか分かります。 – roro

+0

なぜptr2-> downcast()の静的型はベースですか?仮想関数が使用されているときは常にベース関数の戻り型ですか?ランタイムの知識は考慮に入れているので有効ではないことは分かっていますが、type_infoライブラリのtypeidは、派生オブジェクトを返すことを示しています。 – roro

+0

@roro: "静的型"は*宣言型です。あなたの例で 'ptr2'は' base * 'として宣言されています。だから 'ptr2-> downcast()'は 'base :: downcast()'です。 'base :: downcast()'は戻り値の型 'base&'で宣言されます。したがって、静的な観点から、 'ptr2-> downcast()'は 'base'型の左辺値です。オーバーロードの解決は完全に静的型に基づいています。一方、 'typeid'は*実行時*機能です。多型オブジェクトの場合は、* dynamic *型を返します。 'typeid'は' ptr2-> downcast() 'が' derived'オブジェクトにバインドされた 'base&'参照を返すことを伝えます。 – AnT

0

これはやや一般的なやり方で行うことができます。

template<class...>struct types{}; 

template<std::size_t I>using index=std::integral_constant<std::size_t, I>; 

template<class T, class types> 
struct get_index_of_type; 
template<class T, class...Ts> 
struct get_index_of_type<T, types<T,Ts...>>: 
    index<0> 
{}; 
template<class T, class U, class...Ts> 
struct get_index_of_type<T, types<U,Ts...>>: 
    index<get_index_of_type<T, types<Ts...>>{}+1> 
{}; 

template<class R, class Types> 
struct dynamic_dispatch; 
template<class R, class...Ts> 
struct dynamic_dispatch<R, types<Ts...>> 
{ 
    using fptr = R(*)(void const* pf, void* t); 

    template<class F> 
    std::array<fptr, sizeof...(Ts)> 
    make_table() const { 
    return {{ 
     +[](void const* pf, void* t)->R{ 
     auto* pt = static_cast< std::remove_reference_t<Ts>* >(t); 
     auto* f = static_cast< std::remove_reference_t<F> const* >(pf); 
     return (*f)(static_cast<Ts&&>(*pt)); 
     }... 
    }}; 
    } 
    void const* pf = nullptr; 
    std::array<fptr, sizeof...(Ts)> table; 

    dynamic_dispatch(dynamic_dispatch&&)=default; 
    dynamic_dispatch(dynamic_dispatch const&)=default; 
    dynamic_dispatch& operator=(dynamic_dispatch&&)=default; 
    dynamic_dispatch& operator=(dynamic_dispatch const&)=default; 

    template<class F, 
    std::enable_if_t< !std::is_same<std::decay_t<F>, dynamic_dispatch>{}, int> =0 
    > 
    dynamic_dispatch(F&& f): 
    pf(std::addressof(f)), 
    table(make_table<std::decay_t<F>>()) 
    {} 

    template<class T> 
    R operator()(T&& t) const { 
    return table[get_index_of_type<T,types<Ts...>>{}](pf, std::addressof(t)); 
    } 
}; 

dynamic_dispatch<R, types<a,b,c>>

が必要なL/Rの値とconstのを含め abまたは c(正確にタイプのいずれかを呼び出すことができる任意の呼び出し可能になりますので、詳細なリストを作るん暗黙のキャストが行われていません;これはできますより多くの作業で修正することができます)。今

applyと呼ばbaseにメソッドを追加:

virtual void apply(dynamic_dispatch<void, types<base*, derived*>> f) { 
    return f(this); 
} 

オーバーライドそれを派生で:

virtual void apply(dynamic_dispatch<void, types<base*, derived*>> f) override { 
    return f(this); 
} 

を同じ身体で。メインで今

auto super_foo = [](auto* x) { 
    return foo(*x); 
}; 

int main() 
{ 
    base* ptr1 = new(base); 
    base* ptr2 = new(derived); 

    ptr1->apply(super_foo); 
    ptr2->apply(super_foo); 
} 

live example

さらに読むために、関数オブジェクト上の型のリストに対して消去された動的ディスパッチをタイプします。私はこのディスパッチのためのビューを作成しました。

super_fooは、全体のオーバーロードセットfooを表す単一のオブジェクトであり、1つのパラメータとして渡すことができます。

これはビジターパターンでより多くの従来もを行うことができます。そして、あなたがfoo_visitorを実装

struct visitor { 
    void invoke(base*) const = 0; 
    void invoke(derived*) const = 0; 
}; 

struct foo_visitor:visitor { 
    void invoke(base* a) const {return foo(*a);} 
    void invoke(derived* a) const {return foo(*a);} 
}; 

、我々はvisitor&を取り、それに.invoke(this)を行うapplyを書きます。

(注)このテクニックこと、あなたはそれがスマートに正確な一致を必要とTs代わりの間で選択させる場合は特に、再帰を介して複数の派遣多型を可能にする(または、複数の種類のパックでdynamic_dispatch< R, types... >dynamic_dispatchを変えることができます)。

関連する問題