質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.00%

メンバ関数テンプレートを仮想関数にできないので困ってます

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,415

Chironian

C++総合1位

【追記】不可能な内容かも知れないのですが、まだ少しだけ希望を持っていますので、「解決済」には致しません。もし、解をお持ちの方がいらっしゃいましたら、是非ご教授下さい。

【2016/12/22追記】
長らく放置してしまいました。断念したのでクローズします。


今、あるライブラリをC++11準拠の条件で開発してます。
コンパイラは、msvc 2015とg++4.9.2以降を想定しています。

イメージ的には、下記のlib.hを開発してユーザさんへ提供し、ユーザさんがmain.cppを開発します。
Client()関数の中身が結構大きくなりそうなので、できればClient()関数をテンプレートではなく通常の関数にしたいのです。

このような場合、通常はLib<>クラスを基底クラスから派生するようにし、Func()関数を仮想関数にすると思います。しかし、関数テンプレートを仮想関数にすることができませんので、Client()を非テンプレート化できないのです。
仮想関数に代わる何か良い方法はないでしょうか?

Typeには制限を付けられませんが、valueはあまり無茶に増えることはないので、現時点ではClient()を関数テンプレートのままとし、使用するvalueについて明示的実体化することで考えています。
しかし、もし、方法があればClient()を通常の関数で開発できるようにしたいのです。

現時点では無理でも、C++14やC++17等で対応できる可能性があればその情報を頂けるとありがたいです。

// lib.h
#include <iostream>
#include <typeinfo>

template<int value>
struct Lib
{
    template<typename Type>
    void Func(const Type& aInstance)
    {
        std::cout << value << " :" << typeid(aInstance).name() << "\n";
        // 実際にはここで下記シングルトンを生成しています
        // template<typename Type, int value>
        // class Singleton {};
    }
};
// main.cpp
#include "lib.h"

class Foo {};

template<int value>
void Client(Lib<value>* aLib)
{
    aLib->Func(123);
    aLib->Func(123.0);
    aLib->Func(Foo());
}

int main()
{
    Lib<456>    lib_456;
    Lib<789>    lib_789;

    Client(&lib_456);
    Client(&lib_789);
}

【追記】
サンプルの説明が不適切だったので、Func()内のコメントを修正しました。

また、何のためにSingleton<Type, value>を作っているのか分からないと思います。
かなり長くなってしまうのですか、実際に使っているものに近いサンプルを以下に示します。
boostで使われているテクニックですが、シングルトン生成時、スタティック変数Instanceをこのように初期化することで、main()関数が走る前にこのシングルトンが生成されます。
これにより、Lib<>のコンストラクタで、Lib<>::Func()に渡されるTypeの型リストへアクセスできるようになるのです。
このタイミングではLib<>がコンストラクトされていないので、動的な方法ではvalueをリストできないのです。

こんなに複雑なことをしている目的は、Lib<>のインスタンスで使われているTypeのリストをvalueと関連づけて、Lib<>のコンストラクタ内で取り出せるようにすることです。

// lib.h
#include <iostream>
#include <typeinfo>
#include <vector>

class BaseSingleton;
std::vector<BaseSingleton*> TypeList;

class BaseSingleton
{
public:
    virtual void print(int aValue) = 0;
};

template<typename Type, int value>
class Singleton : public BaseSingleton
{
private:
    static Singleton& Instance;
    Singleton()
    {
        std::cout << "Singleton() : " << value << " :" << typeid(Type).name() << "\n";
    }
    static void use(const Singleton&) {}

public:
    static Singleton& getInstance()
    {
        static Singleton instance;
        use(Instance);
        TypeList.push_back(&instance);
        return instance;
    }

    void print(int aValue)
    {
        if (aValue == value)
            std::cout << "print()     : " << value << " :" << typeid(Type).name() << "\n";
    }

    // コピー/ムーブ禁止
    Singleton(const Singleton&)  = delete;
    Singleton(      Singleton&&) = delete;
    Singleton& operator=(const Singleton&)  = delete;
    Singleton& operator=(      Singleton&&) = delete;
};
template<typename Type, int value>
Singleton<Type, value>& Singleton<Type, value>::Instance = Singleton<Type, value>::getInstance();

template<int value>
struct Lib
{
    Lib()
    {
        // ここで自分で使われている型のリストを抽出してます
        for (auto& itr : TypeList) {
            itr->print(value);
        }
    }

    template<typename Type>
    void Func(const Type& aInstance)
    {
        Singleton<Type, value>::getInstance();

        // 他にSFINAEを使ってTypeで分岐し、下記のようなイメージの処理をします
        //   組み込み型の時は特定の処理を行う
        //   関数bar()を持つclassなら、bar()を呼ぶ
        //   関数boo()を持つclassなら、boo()を呼ぶ
        //   それ以外なら、static_assert(false)する
    }
};
// main.cpp
#include "lib.h"

class Foo {};
template<int value>
void Client(Lib<value>* aLib)
{
    aLib->Func(123);
    aLib->Func(123.0);
    aLib->Func(Foo());
}

class Bar {};
template<int value>
void Client2(Lib<value>* aLib)
{
    aLib->Func("string");
    aLib->Func(Bar());
}

int main()
{
    std::cout << "---------- Start main()\n";

    std::cout << "pre Lib<456>\n";
    Lib<456>    lib_456;
    std::cout << "post Lib<456>\n";
    Client(&lib_456);

    std::cout << "pre Lib<789>\n";
    Lib<789>    lib_789;
    std::cout << "post Lib<789>\n";
    Client(&lib_789);

    std::cout << "pre Lib<-123>\n";
    Lib<-123>   lib_m123;
    std::cout << "post Lib<-123>\n";
    Client2(&lib_m123);
}


msvc2015での実行結果は下記の通りです。

Singleton() : 456 :int
Singleton() : 456 :double
Singleton() : 456 :class Foo
Singleton() : 789 :int
Singleton() : 789 :double
Singleton() : 789 :class Foo
Singleton() : -123 :char [7]
Singleton() : -123 :class Bar
---------- Start main()
pre Lib<456>
print()     : 456 :int
print()     : 456 :double
print()     : 456 :class Foo
post Lib<456>
pre Lib<789>
print()     : 789 :int
print()     : 789 :double
print()     : 789 :class Foo
post Lib<789>
pre Lib<-123>
print()     : -123 :char [7]
print()     : -123 :class Bar
post Lib<-123>

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

check解決した方法

0

長い間放置してしまいました。すいません。

結局断念しました。
msvcとgccでは問題ないです。
MinGWにはコンパイル単位内のクラス数が増えすぎるとビルドできない不具合があり、それにハマると痛いのですが、最悪コンパイル単位の分割で対処できますので断念します。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

このような場合、通常はLib<>クラスを基底クラスから派生するようにし、Func()関数を仮想関数にすると思います。
これがよく判りません。Lib<>クラスを基底クラスから派生させるために、わざわざFunc関数を仮想関数にする必要はないように思うのですが。

一応、以下のようなコードでClient関数のtemplateは外せますが、こんな単純な話ではないですか?
// 基底クラスにする
struct LibBase
{
    int value;        // valueはメンバ変数にしてしまう

    LibBase(int aValue)
        : value(aValue)
    {}
    virtual ~LibBase()
    {}

    template<typename Type>
    void Func(const Type& aInstance)
    {
        std::cout << value << " :" << typeid(aInstance).name() << "\n";
        // 実際にはSFINAEを使ってTypeで分岐し、下記のようなイメージの処理をします
        //   組み込み型の時は特定の処理を行う
        //   関数bar()を持つclassなら、bar()を呼ぶ
        //   関数boo()を持つclassなら、boo()を呼ぶ
        //   それ以外なら、static_assert(false)する
    }
};

// 派生してクラステンプレート化
template<int Value>        // メンバ変数と名前がぶつかるのでちょっと変更
struct Lib : public LibBase
{
    Lib()
        : LibBase(Value)
    {}
};
// 基底クラスで受け取るのでtemplate不要
void Client(LibBase* aLib)
{
    aLib->Func(123);
    aLib->Func(123.0);
    aLib->Func(Foo());
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/11/12 09:52

    ああ、ごめんなさい。丸っと大事な説明が漏れてました。
    valueもFunc()の中で「静的」に処理する必要があるので、テンプレート引数にしています。
    Func()の中では、Typeとvalueをテンプレート引数とするクラス・テンプレートを使っているのです。

    キャンセル

  • 2015/11/12 14:23

    やはりそうでしたか。そうなると確かに難問ですね。

    キャンセル

0

サンプルの実行結果に沿って改変してみました。
この変更でどうでしょう?

変更点は
LibBaseのFuncを特殊化で解決。
そのためClient側のchar [7] は std::stringに変更
関数boo, barの呼び分けが出来ない点は
共通のFunc関数からClient側で判断しています。

static_assertが書けない点はClientがFuncを定義していないと
ビルドエラーになるので、それを代替案としています。

#if 0
    template<typename Type>
    void Func(const Type& aInstance)
    {
        Singleton<Type, value>::getInstance();

        // 他にSFINAEを使ってTypeで分岐し、下記のようなイメージの処理をします
        //   組み込み型の時は特定の処理を行う
        //   関数bar()を持つclassなら、bar()を呼ぶ
        //   関数boo()を持つclassなら、boo()を呼ぶ
        //   それ以外なら、static_assert(false)する
    }
#else

    template<typename Type>
    void Func(const Type& aInstance)
    {
        Singleton<Type, value>::getInstance();

        aInstance.Func();

        // static_assertは書けないが、Funcが無ければビルドエラーとなる。
    }

    template<>
    void Func<int>(const int& aInstance)
    {
        Singleton<int, value>::getInstance();
        // 組み込み型の特定処理。
    }

    template<>
    void Func<double>(const double& aInstance)
    {
        Singleton<double, value>::getInstance();
        // 組み込み型の特定処理。
    }

    template<>
    void Func<std::string>(const std::string& aInstance)
    {
        Singleton<std::string, value>::getInstance();
        // 組み込み型の特定処理。
    }

#endif

#if 0
class Foo {};
#else
class Foo
{
public:
    void Func() const
    {
        boo();
    }
    void boo() const
    {
        printf("function boo()\n");
    }
};
#endif

#if 0
class Bar {};
#else
class Bar
{
public:
    void Func() const
    {
        bar();
    }
    void bar() const
    {
        printf("function bar()\n");
    }
};
#endif

実行結果
Singleton() : -123 :class std::basic_string<char,struct std::char_traits<char>,c
lass std::allocator<char> >
Singleton() : 789 :double
Singleton() : 789 :int
Singleton() : 456 :double
Singleton() : 456 :int
Singleton() : 456 :class Foo
Singleton() : 789 :class Foo
Singleton() : -123 :class Bar
---------- Start main()
pre Lib<456>
print()     : 456 :double
print()     : 456 :int
print()     : 456 :class Foo
post Lib<456>
function boo()
pre Lib<789>
print()     : 789 :double
print()     : 789 :int
print()     : 789 :class Foo
post Lib<789>
function boo()
pre Lib<-123>
print()     : -123 :class std::basic_string<char,struct std::char_traits<char>,c
lass std::allocator<char> >
print()     : -123 :class Bar
post Lib<-123>
function bar()

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/11/13 13:05

    すいません、気がつくのが遅れました。
    ご回答ありがとうございます。

    目的はClient()を非テンプレートにすることなので、折角ご回答頂いたのですが、狙いが異なるように思います。

    SFINAEによる分岐処理は既に成功しています。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.00%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる