c++ - 尽管类型删除,是否可以使用静态多态性(模板)?

标签 c++ c++11 polymorphism type-erasure

在使用了 Java 数十年后,我最近又回到了 C++,目前我正在为应用了类型删除的实例使用基于模板的数据转换方法而苦苦挣扎。请耐心等待,我的命名法可能仍然不适合 C++ 本地人。

这就是我要实现的目标:

  • 实现能够容纳任何值类型的动态变量
  • 使用各种其他表示形式(字符串、整数、二进制...)访问这些变量的内容
  • 能够在容器中保存变量实例,独立于它们的值类型
  • 使用转换函数在变量值和表示之间转换
  • 能够通过提供新的转换函数来引入新的表示
  • 约束:尽可能只使用 C++-11 特性,不使用 boost::any 等库。

这个的粗略草图可能是这样的:

#include <iostream>
#include <vector>

void convert(const std::string &f, std::string &t) { t = f; }
void convert(const int &f, std::string &t) { t = std::to_string(f); }
void convert(const std::string &f, int &t) { t = std::stoi(f); }
void convert(const int &f, int &t) { t = f; }

struct Variable {
  virtual void get(int &i) = 0;
  virtual void get(std::string &s) = 0;
};
template <typename T> struct VariableImpl : Variable {
  T value;
  VariableImpl(const T &v) : value{v} {};
  void get(int &i) { convert(value, i); };
  void get(std::string &s) { convert(value, s); };
};

int main() {
  VariableImpl<int> v1{42};
  VariableImpl<std::string> v2{"1234"};

  std::vector<Variable *> vars{&v1, &v2};

  for (auto &v : vars) {
    int i;
    v->get(i);
    std::string s;
    v->get(s);

    std::cout << "int representation: " << i <<
        ", string representation: " << s << std::endl;
  }

  return 0;
}

代码做了它应该做的,但显然我想摆脱 Variable::get(int/std::string/...) 而是模板化它们,因为否则每个新表示都需要一个定义和一个实现,而后者与所有其他表示完全相同。

到目前为止,我已经尝试过各种方法,例如虚拟模板化、方法、将 CRDT 应用于中间类型、各种形式的包装器,但在所有这些方法中,我都被 VariableImpl< 的删除值类型所困扰。一方面,我认为可能没有解决方案,因为在类型删除之后,编译器不可能知道它必须生成哪些模板化的 getter 和转换器调用。另一方面,我认为我可能在这里遗漏了一些真正重要的东西,尽管有上述限制,但应该有一个解决方案。

最佳答案

这是一个典型的双重分派(dispatch)问题。这个问题的通常解决方案是使用某种调度程序类,其中包含您要调度的函数的多个实现(在您的情况下为 get )。这称为访客模式。它的众所周知的缺点是它创建的依赖循环(层次结构中的每个类都依赖于层次结构中的所有其他类)。因此,每次添加新类型时都需要重新访问它。再多的模板魔法也无法消除它。

您没有专门的访问者类,您的变量作为其自身的访问者,但这是一个次要的细节。

既然您不喜欢这个解决方案,那么还有一个。它使用在运行时填充的函数注册表,并以其参数的类型标识为关键字。这有时称为“非循环访问者”。

这是针对您的案例的半生不熟的 C++11 友好实现。

#include <map>
#include <vector>
#include <typeinfo>
#include <typeindex>
#include <utility>
#include <functional>
#include <string>
#include <stdexcept>

struct Variable
{
    virtual void convertValue(Variable& to) const = 0;
    virtual ~Variable() {};

    virtual std::type_index getTypeIdx() const = 0;

    template <typename K> K get() const;

    static std::map<std::pair<std::type_index, std::type_index>,
         std::function<void(const Variable&, Variable&)>>
             conversionMap;

    template <typename T, typename K>
    static void registerConversion(K (*fn)(const T&));
};


template <typename T>
struct VariableImpl : Variable
{
    T value;

    VariableImpl(const T &v) : value{v} {};
    VariableImpl() : value{} {}; // this is needed for a declaration of 
                                 // `VariableImpl<K> below
                                 // It can be avoided but it is 
                                 // a story for another day

    void convertValue(Variable& to) const override
    {
        auto typeIdxFrom = getTypeIdx();
        auto typeIdxTo = to.getTypeIdx();

        if (typeIdxFrom == typeIdxTo) // no conversion needed
        {
            dynamic_cast<VariableImpl<T>&>(to).value = value;
        }
        else
        {
            auto fcnIter = conversionMap.find({getTypeIdx(), to.getTypeIdx()});
            if (fcnIter != conversionMap.end())
            {
                fcnIter->second(*this, to);
            }
            else
                throw std::logic_error("no conversion");
        }
    }

    std::type_index getTypeIdx() const override
    {
        return std::type_index(typeid(T));
    }
};

template <typename K> K Variable::get() const
{
    VariableImpl<K> vk;
    convertValue(vk);
    return vk.value;
}

template <typename T, typename K>
void Variable::registerConversion(K (*fn)(const T&))
{
    // add a mutex if you ever spread this over multiple threads
    conversionMap[{std::type_index(typeid(T)), std::type_index(typeid(K))}] = 
        [fn](const Variable& from, Variable& to) {
            dynamic_cast<VariableImpl<K>&>(to).value = 
              fn(dynamic_cast<const VariableImpl<T>&>(from).value);
        };
}

现在你当然需要调用 registerConversion 例如在 main 的开头并将每个转换函数传递给它。

Variable::registerConversion(int_to_string);
Variable::registerConversion(string_to_int);

这并不理想,但几乎没有什么是理想的。


说了这么多,我建议您重新审视您的设计。你真的需要所有这些转换吗?为什么不选择一种表现形式并坚持下去呢?

关于c++ - 尽管类型删除,是否可以使用静态多态性(模板)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73077061/

相关文章:

C++符号分析: how to determine which static initialization is performed?

c++ - 使用迭代器构造 std::vector 时不需要保留吗?

c++ - QFileDialog unicode目录不可用

C++ 模板运算符重载不同类型,自动返回类型

c++ - 在没有任何锁的情况下加入另一个线程后是否需要内存防护?

c++ - 将 std::map 写入/读取到二进制文件需要运算符

java - 使用instanceof或public方法获取实例

具有记录和类类型的 Haskell 多态函数

c++ - C++中 vector 上的未初始化局部变量错误

java - 为什么一个类返回它自己的一个实例?