none
テンプレート関数の明示特殊化は仮想関数にできる? RRS feed

  • 質問

  • テンプレート関数が仮想関数にできないのは仕様と知っているのですが、テンプレート関数の明示特殊化は仮想関数にできるのでしょうか?

    以下のコードはとりあえずコンパイルが通ってしまいます。


    template<typename T = void>
    struct acnv;

    template<>
    struct acnv<void> abstract{
        template<typename U>
        void conv(U*) const{}
        virtual void conv<>(signed char *) const = 0;
        virtual void conv<>(unsigned char *) const = 0;
        virtual void conv<>(signed short *) const = 0;

        //virtual void hoge() = 0;    //この行をコメントでなくすると、aconv<int>はhogeがないのでインスタンス化できない
    };

    template<typename T>
    struct acnv : acnv<>{
        T v;

        template<typename U>
        void conv(U *p) const{
            *p = v;
        }

        //virtual void conv<>(signed char *) constを定義しなくても
        //template<typename U> void conv<>(U *p) constで十分なのかコンパイルができる
        //virtual void conv<>(signed char *p) const{
        //    *p = v;
        //}
    };

     

    acnv<> *t1 = new acnv<int>;

    signed char t2;
    //t1->conv(&t2);    //この行をコメントでなくすると、コンパイラがクラッシュする

     

    テンプレート関数を定義し、それの明示特殊化をいくつか仮想関数で宣言します。

    派生クラスでは、明示特殊化された関数を定義しなくてもコンパイルが通ってしまいます。

    ただし、その仮想関数を呼び出そうとすると、コンパイラがクラッシュします。

     

    このコードの目的は、variant型のようなものを作ることです。

    ただし、intからshortの変換などを全て記述せずできるだけジェネリック的に記述したいとおもっています。

    現在何の型を保持しているかという情報を継承に任せるところまでは実現できましたが、
    現在保持している型から要求された型への変換を記述しようとしたところで、
    テンプレート関数が仮想関数にできないので詰まっていた所、
    明示特殊化した関数を仮想関数にした所、コンパイルが通ってしまったので、
    このような疑問に陥りました。

     

    テンプレート関数の明示特殊化を仮想関数にできるのは仕様ですか?

    仕様であるとすれば、その仮想関数が呼び出せないのはVCのバグですか? このバグは認知されていますか?

    仕様でないとすれば、このコードがコンパイルできてしまうのはVCのバグですか? このバグは認知されていますか?

    2011年9月7日 5:44

回答

  • 規格・仕様面については何とも言いかねますが、一点だけ。

    「バグが認知されているか」というのは、Microsoft に対してでしょうか?
    この場合、Microsoft 自身に聞くか、Connect や Blog などでそういった情報を見つけない限りわかりません。
    フォーラムで聞くよりは、直接 Connect に不具合(仕様であっても、なくても、結果の挙動は不具合では?)として登録してみてください。
    できれば、日本語 と 英語の Connect ですでに登録されていないかどうか確認いただければなおよいでしょう。
    (登録は 日本語側の Connect でよいと思います)


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク はぴぴ 2011年9月12日 7:00
    2011年9月7日 13:57
    モデレータ

すべての返信

  • 規格・仕様面については何とも言いかねますが、一点だけ。

    「バグが認知されているか」というのは、Microsoft に対してでしょうか?
    この場合、Microsoft 自身に聞くか、Connect や Blog などでそういった情報を見つけない限りわかりません。
    フォーラムで聞くよりは、直接 Connect に不具合(仕様であっても、なくても、結果の挙動は不具合では?)として登録してみてください。
    できれば、日本語 と 英語の Connect ですでに登録されていないかどうか確認いただければなおよいでしょう。
    (登録は 日本語側の Connect でよいと思います)


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク はぴぴ 2011年9月12日 7:00
    2011年9月7日 13:57
    モデレータ
  • 回答ありがとうございます。 すいません、モデレータというのがMicrosoftの人だと勘違いしていました。 教えていただいたところで報告してみようと思います。
    2011年9月8日 2:30
  • テンプレート関数の明示特殊化で template <> を省略できましたっけ?

    具体的には、

    virtual void conv<>(signed char *) const = 0;

    ではなく、

    template <> virtual void conv<>(signed char *) const = 0; // 注意: コンパイル不可

    といった形になるのではないでしょうか。

    また、こうやって template <> を付けると template のように見えてくるので、仮想関数にはできないという風に解釈されても仕方が無いかと思います。

    一方、明示的に特殊化した時点で意味的にはもはや template では無くなっていますし、template + virtual の仮想関数テーブル問題も発生しませんので、これは許されても良いのかなという気もしてきます。

    一応 C++ の規格書は眺めてみましたが、「これだ!」という記述を見つけることはできませんでした。回答になっていなくてスミマセン。議論が盛り上がって、何らかの答えに辿りつけたら良いなという期待を込めて投稿します。

    ところで、メンバテンプレートの一部を仮想化したいだけなら、普通にオーバーロードしてしまえば良いように思うのですが、これだと問題を単純化しすぎていますか? 以下、簡単なサンプルです。

    struct Base {
    	template <typename U> void f(U) {
    		cout << "Base::f(U) called" << endl;
    	}
    	virtual void f(int) = 0;
    };
    
    struct D1: Base {
    	using Base::f;
    	virtual void f(int) {
    		cout << "D1::f(int) called" << endl;
    	}
    };
    
    struct D2: Base {
    	using Base::f;
    	virtual void f(int) {
    		cout << "D2::f(int) called" << endl;
    	}
    };
    
    void Test() {
    	auto_ptr<Base> pD1(new D1);
    	auto_ptr<Base> pD2(new D2);
    
    	pD1->f(0.1); // Base::f(U) called
    	pD1->f(100); // D1::f(int) called
    	pD2->f(0.1); // Base::f(U) called
    	pD2->f(100); // D2::f(int) called
    }
    
    


    また、どんなコードであれ、コンパイルしただけでコンパイラがクラッシュするなら、それは間違いなくコンパイラのバグです。手元の VS2008, VS2010 で試してみましたが、どちらも以下のコードで「ごめんなさい」ダイアログが出てコンパイルできませんでした。

    struct Base {
    	template <typename U> void f(U) {
    		cout << "Base::f called" << endl;
    	}
    	virtual void f<>(int) {
    		cout << "Base::f<int> called" << endl;
    	}
    };
    
    struct Derived: public Base {
    	using Base::f;
    	virtual void f<>(int) {
    		cout << "Derived::f<int> called" << endl;
    	}
    };
    
    void Test() {
    	Derived d;
    	d.f(100);
    }
    
    

    2011年9月8日 13:13
  • 本筋じゃなくてすみません。

    モデレータというのがMicrosoftの人だと勘違いしていました。

    モデレータは現状、Microsoft 関係者のみだったと思います。
    勘違いではないですが、モデレータ各位は利用者同士にもめ事の仲介、解決したと思われるスレッドの処理が主で、未解決スレッドにおいては公開されている情報を紹介する程度にとどまり、新しい情報や会社の公式見解を聞くことはできません。

    開発チームの方からコメントをもらえる可能性のあるチャンネルは、Visual Studio の Connect が有名ですね。こちらは日本語で投稿できます。(翻訳するスタッフが介在します)
    その他の場所で可能性があるとすると、blog か US Forums で英語でになると思われます。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年9月8日 14:59
    モデレータ
  • Connectに登録した所、バグとして認識され、修正を検討するとのことでした。

    これから、このような問題はConnectの方に投げたいと思います。

    ありがとうございました。

    2011年9月12日 6:29
  • 回答ありがとうございます。

    仮想関数でない場合では

    void conv<>(signed char *)

    とするとコンパイルできてしまうのですが、これは単なるオーバーロードと同じなんでしょうか。

    目的は、限られた個数の型を扱えるvariant型なので、オーバーロードでも実現できるのですが、数が多く冗長なので、できればまとめたいと思いました。

    テンプレートが仮想関数にできないのは、無限のパターンができてしまい、仮想関数テーブルにそれを登録しなければならないからできないという理由だとすると、
    仮想化するものだけ特殊化してやれば、原理的にはできそうな気がするので、純粋仮想関数を宣言する所だけ冗長に書き、
    継承したほうは1つのテンプレートで書けることを期待して書いてみた所、このようなバグを発見しました。

    ですが、実際のところ仮想関数テーブルにはvirtual void conv<>(signed char *) const = 0;は登録されていないどころか、
    dynamic_castしようとした所、ポリモーフィック型でないとエラーが出てしまいました。

    2011年9月12日 6:58
  • 回答ありがとうございます。

    モデレータについて理解しました。

    Connectに投稿し、答えが得られました。

    2011年9月12日 7:00