自动推断返回类型

时间:2020-01-02 11:26:55

标签: c++ templates template-meta-programming

在阅读C++ auto deduction of return typeC++ : Vector of template class之后,我仍然想知道如何对对象执行通用操作(例如 运算符<<重载)。我的代码看起来像

#include <map>
#include <memory>
#include <string>
#include <iostream>

/**
 * Abstract placeholder for Cache polymorphism
 */
class ICache
{
public:

    virtual void update() {};

    friend std::ostream & operator << (std::ostream & out, const ICache & IC)
    {
        out << "you should never print this";
    }
};

/**
 * Concrete. Coupling a name with some cached value
 */
template<typename T>
class Cache :
    public ICache
{
    const std::string m_name;
    T m_cached;

public:

    Cache(const std::string & name) :
        m_name(name),
        m_cached(0)
    {}

    void update() override
    {
        // m_cached is synced with remote resource; action depends both from T and m_name
    }

    void set(const T t)
    {
        std::cout << m_name << " setting " << +m_cached << " -> " << +t << std::endl;
        m_cached = t;
    }

    inline T get() const noexcept { return m_cached; }

    friend std::ostream & operator << (std::ostream & out, const Cache & O)
    {
        out << "Cache<" << O.m_name << ", " << O.m_cached << ">";
    }
};

class CacheMap
{
    std::map<std::string, std::unique_ptr<ICache>> m_map;

    template<typename T>
    Cache<T>* _get_ptr(const std::string & name) const
    {
        return reinterpret_cast<Cache<T>*>(m_map.at(name).get());
    }

public:

    template<typename T>
    T get(const std::string & name) const
    {
        return _get_ptr<T>(name)->get();
    }

    template <typename T>
    void set(const std::string & name, T t)
    {
        _get_ptr<T>(name)->set(t);
    }

    template <typename T>
    void insert(const std::string & name, T def = 0)
    {
        std::unique_ptr<ICache> up = std::make_unique<Cache<T>>(name);
        m_map.insert({name, std::move(up)});
        set<T>(name, def);
    }

    friend std::ostream & operator << (std::ostream & out, const CacheMap & OM)
    {
        out << "OM{";
        for (const auto & IO : OM.m_map)
            out << IO.first << ": " << *(IO.second.get()) << ", ";     // ver1
            // out << IO.first << ": " << (IO.second->get()) << ", ";  // ver2
        out << "}";
        return out;
    }
};


int main()
{
    CacheMap m;
    int i= 70000;

    m.insert<int>("i", 69999);
    m.insert<short>("s", 699);
    m.insert<char>("c", 69);
    m.set("i", i);

    std::cout << m << std::endl;
}

标有结尾//ver1的行显示you should never print this有意义;我正在处理std::unique_ptr<ICache>个对象。

标有结尾//ver2的行根本不会编译,这也是有道理的。

我想要做的 是在运行时(听起来很糟糕)在运行时自动检测到正确的CacheMap,并提供了地图密钥。以便触发正确的T并检索reinterpret_cast<>的值。

修改1

如果使用m_cached进行编译,则g++ -O3行会导致分段违规。

3 个答案:

答案 0 :(得分:2)

只需使用虚函数即可。将变量从Cache<int>指针类型转换为ICache指针的时间会丢失有关它的编译时间信息。该信息丢失。您可以在dynamic_cast中使用friend ICache::operator<<处理所有不同的类型...要正确解析类型信息,请使用virtual函数-即。与每个班级相关的唯一数据。

#include <map>
#include <memory>
#include <string>
#include <iostream>

class ICache
{
public:
    virtual ~ICache() {};
    virtual void update() {};

    // -------- HERE --------------
    virtual std::ostream& printme(std::ostream & out) const = 0;
    friend std::ostream& operator << (std::ostream & out, const ICache & IC) {
        return IC.printme(out);
    }

};

template<typename T>
class Cache : public ICache {
    const std::string m_name;
    T m_cached;
public:
    Cache(const std::string & name): m_name(name), m_cached(0) {}
    void update() override {}
    void set(const T t) {
        std::cout << m_name << " setting " << +m_cached << " -> " << +t << std::endl;
        m_cached = t;
    }
    inline T get() const noexcept { return m_cached; }
    std::ostream& printme(std::ostream & out) const override {
        out << "Cache<" << m_name << ", " << m_cached << ">";
        return out;
    }
};

class CacheMap {
    std::map<std::string, std::unique_ptr<ICache>> m_map;
    template<typename T>
    Cache<T>* _get_ptr(const std::string & name) const {
        return dynamic_cast<Cache<T>*>(m_map.at(name).get());
    }
public:
    template<typename T>
    T get(const std::string & name) const {
        return _get_ptr<T>(name)->get();
    }

    template <typename T>
    void set(const std::string & name, T t) {
        _get_ptr<T>(name)->set(t);
    }

    template <typename T>
    void insert(const std::string & name, T def = 0) {
        std::unique_ptr<ICache> up = std::make_unique<Cache<T>>(name);
        m_map.insert({name, std::move(up)});
        set<T>(name, def);
    }

    friend std::ostream& operator << (std::ostream & out, const CacheMap & OM) {
        out << "OM{";
        for (const auto & IO : OM.m_map)
            out << IO.first << ": " << *(IO.second.get()) << ", ";     // ver1
            // out << IO.first << ": " << (IO.second->get()) << ", ";  // ver2
        out << "}";
        return out;
    }
};


int main()
{
    CacheMap m;
    int i= 70000;

    m.insert<int>("i", 69999);
    m.insert<short>("s", 699);
    m.insert<char>("c", 69);
    m.set("i", i);

    std::cout << m << std::endl;
}

output on godbolt

i setting 0 -> 69999
s setting 0 -> 699
c setting 0 -> 69
i setting 69999 -> 70000
OM{c: Cache<c, E>, i: Cache<i, 70000>, s: Cache<s, 699>, }

我刚刚发现,为了防止非常严重且难以调试的错误,我提醒您使用dynamic_cast中的reintepret_cast而不是CacheMap::_get_ptr()

答案 1 :(得分:2)

有多种方法,但是通常我会实现一个调用虚拟函数的operator<<()

class ICache {
   protected:      //  so the function is only accessible to derived classes

    virtual std::ostream print(std::ostream &out) const = 0;    // force derived classes to override

  friend std::ostream &operator<<(std::ostream &out, const ICache& c);
};

然后将运算符的单个定义<<放在单个编译单元中

// definition of class ICache needs to be visible here

std::ostream &operator<<(std::ostream &out, const ICache& c)
{
     return c.print(out);
}   

并实现派生类

// definition of ICache here

template<class T>
class Cache: ICache
{
   protected:
    std::ostream print(std::ostream &out) const override
    {
        // output a Cache<T> 
        return out;
    }
};

这样做的优点是每个类都负责输出自身,而不是容器类必须确定要调用哪个输出函数(以及程序员有机会忘记执行此操作)。

答案 2 :(得分:1)

首先,reinterpret_cast是一件危险的事情。在多态的情况下,您通常希望使用dynamic_cast

您的问题根本不是模板等问题,而是更多的是您想将operator<<虚拟化,这是不可能的,因为它是一个朋友函数。一个简单的解决方法如下:

class ICache {
  virtual void print(std::ostream &out) const { // Prefer pure virtual 
    out << "Never print this\n";
  }
  friend std::ostream &operator<<(std::ostream &out, const ICache& c) {
    c.print(out);
    return out;
  }
};

template<class T>
class Cache: ICache {
  void print(std::ostream &out) const override {
    out << "Print this instead\n";
  }
};

无需任何强制转换即可完成您想要的工作。

相关问题