【问题标题】:Calling the templated function of a derived type调用派生类型的模板化函数
【发布时间】:2019-12-01 19:06:17
【问题描述】:

我正在尝试对值调用模板化函数,但我想调用来自潜在派生类型的函数的模板化版本。在typeid 期间,已确定类型为Dog,但任何转换尝试都失败了。如何调用模板化的派生类型WriteData函数?

关于示例

  1. WriteData 函数是模板化的,即使在本示例中未使用该值,因为我做了一个小示例。
  2. 并不总是知道Array 参数将是Animal
#include <iostream>
using namespace std;

struct Writer {
    void Write(char* data) {
        cout << data << endl;
    }
};

template<typename Type>
struct Array {
    Type Data[2];

    template<typename Formatter>
    void WriteData(Formatter formatter) {
        cout << "Data[0](typeid): " << typeid(*Data[0]).name() << endl;
        cout << "Data[1](typeid): " << typeid(*Data[1]).name() << endl;

        Data[0]->WriteData(formatter); // "Animal is doing something"
        Data[1]->WriteData(formatter); // "Dog is doing something"

        auto data1cast = reinterpret_cast<decltype(Data[1])>(Data[1]);
        cout << "data1cast(typeidformatter " << typeid(*data1cast).name() << endl; // data1cast(typeid): class Dog
        (*data1cast).WriteData(formatter); // "Animal is doing something"

        ((Dog*)Data[1])->WriteData(formatter); // "Dog is doing something"
    }
};

struct Animal {
    virtual ~Animal() = default;

    template<typename Formatter>
    void WriteData(Formatter formatter) {
        formatter.Write("Animal is doing something");
    }
};

struct Dog : Animal {
    template<typename Formatter>
    void WriteData(Formatter formatter) {
        formatter.Write("Dog is doing something");
    }
};

int main(void) {
    Array<Animal*> arr;
    arr.Data[0] = new Animal();
    arr.Data[1] = new Dog();

    Writer writer;
    arr.WriteData(writer);

    system("PAUSE");

    return 0;
}

输出

Data[0](typeid): struct Animal
Data[1](typeid): struct Dog
Animal is doing something
Animal is doing something
data1cast(typeid): struct Dog
Animal is doing something
Dog is doing something

【问题讨论】:

    标签: c++ c++11 templates c++14 decltype


    【解决方案1】:

    模板和虚拟cannot be mixed:

    template<typename Formatter>
    virtual void WriteData(Formatter formatter);
    

    将是一个无效的声明。

    成员函数模板不能是虚拟的,派生类中的成员函数模板不能覆盖基类中的虚拟成员函数。

    您有多种选择,具体取决于您想要实现的灵活性以及您有哪些其他限制。例如:

    1.

    可以引入抽象类Formatter

    struct Formatter {
        virtual ~Formatter() = default;
        virtual void Write(const std::string&) = 0;
    };
    
    struct Writer : Formatter {
        void Write(const std::string& data) override {
            std::cout << data << std::endl;
        }
    };
    

    并使WriteData 成为非模板:

    struct Animal {
        virtual ~Animal() = default;
    
        virtual void WriteData(Formatter& formatter) {
            formatter.Write("Animal is doing something");
        }
    };
    
    struct Dog : Animal {
        void WriteData(Formatter& formatter) override {
            formatter.Write("Dog is doing something");
        }
    };
    

    2.

    使WriteData成为一个非虚拟模板,调用虚拟成员函数来获取应该传递给Formatter的信息:

    struct Writer {
        template<class T>
        void Write(const T& data) {
            std::cout << data << std::endl;
        }
    };
    
    struct Animal {
        virtual ~Animal() = default;
    
        template<class Formatter>
        void WriteData(Formatter formatter) {
            formatter.Write(MyName() + " is doing something");
        }
    
        virtual std::string MyName() {
            return "Animal";
        }
    };
    
    struct Dog : Animal{
        virtual std::string MyName() {
            return "Dog";
        }
    };
    

    3.

    根本不要使用虚拟,在编译时调度 WriteData 调用。代替数组使用std::tuple 来存储不同的类型:

    struct Writer {
        template<class T>
        void Write(const T& data) {
            std::cout << data << std::endl;
        }
    };
    
    template<typename... Types>
    struct Array {
        std::tuple<Types...> Data;
    
        Array(Types... data) : Data(std::move(data)...) {}
    
        template<typename Formatter>
        void WriteData(Formatter formatter) {
            std::get<0>(Data).WriteData(formatter);
            std::get<1>(Data).WriteData(formatter);
        }
    };
    
    struct Animal {
        template<typename Formatter>
        void WriteData(Formatter formatter) {
            formatter.Write("Animal is doing something");
        }
    };
    
    struct Dog { // can be derived from Animal, but here it doesn't matter
        template<typename Formatter>
        void WriteData(Formatter formatter) {
            formatter.Write("Dog is doing something");
        }
    };
    
    int main() {
        Array<Animal, Dog> arr{Animal(), Dog()};   // <...> can be omitted in C++17
    
        Writer writer;
        arr.WriteData(writer);
    }
    

    4.

    您可以在if-else 分支中使用dynamic_cast

    struct Writer {
        template<class T>
        void Write(const T& data) {
            std::cout << data << std::endl;
        }
    };
    
    struct Animal {
        virtual ~Animal() = default;
    
        template<typename Formatter>
        void WriteData(Formatter formatter) {
            formatter.Write("Animal is doing something");
        }
    };
    
    struct Dog : Animal {
        template<typename Formatter>
        void WriteData(Formatter formatter) {
            formatter.Write("Dog is doing something");
        }
    };
    
    template<typename Type>
    struct Array {
        Type Data[2];
    
        template<typename Formatter>
        void WriteData(Formatter formatter) {
            for (auto d : Data) {
                if (auto dog = dynamic_cast<Dog*>(d))
                    dog->WriteData(formatter);
                else
                    d->WriteData(formatter);
            }
        }
    };
    

    这个设计很糟糕。每次修改动物的层级,都要更新Array::WriteData

    【讨论】:

    • 建议 3 似乎是一个有趣的想法,因为我不希望我的 Writer 有一个基类 (1) 并且不能保证 WriteData 中的数据可能总是完全不同 (2)问题是,虽然我知道 Animal 和 Dog 在这个例子中是值 0 和 1,但我可能不知道在未来所以使用初始化列表构造是不可能的。我试图在本地进行 std::get(1) = Dog() 设置,尽管这似乎无效。有没有办法在构造时不知道 Array 值类型的情况下做到这一点?
    • @judeclarke,选项 3 纯粹是编译时。你必须知道类型。 std::get&lt;i&gt; 只接受编译时常量作为索引。如果您不知道类型,那么您需要运行时调度。你要么在这里使用virtual,要么用dynamic_cast&lt;Derived&gt;(base) 写一些if-else。我会将其添加为选项 4。
    猜你喜欢
    • 2014-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-28
    • 1970-01-01
    • 2016-09-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多