我可以将一个 class 与模板中的另一个相关联吗(使用 C++17 变体)?

Can I associate one class with another from a template (using C++17 variant)?

我有一些代码接受一种类型的 object 并根据第一种类型创建另一种类型的 object。 (类型之间是1->1的关系。)我原来是用hashtable(unordered_map<>)和key根据第一个object的类型来关联一个创建第二个函数 object。但是随着我对自上次 full-time C++ 以来引入的 C++ 功能的了解越来越多,我发现 std::variant<>.

我已成功转换实现以使用此 C++17 功能。不过,剩下的一块还是有点笨重。在实例化第二个 class 的 object 之前,该设计调用第二个 class 的静态成员函数来验证第一个 object 的内容。为了立即处理此问题,我使用了一个访问者结构,其中为每种输入类型重载了函数运算符。

我想知道是否有某种方法可以使用关联模板,而不是仅使用不同类型的复制代码?

我已经尝试查看 std::variant<> 的工作方式,并且我看到了可以使用 .index() 获取类型索引的位置。我可以看到如何根据索引实例化 object,如果我用 object 类型创建第二个 std::variant<>,我可能会使用它。但是,如您所见,在验证参数之前,我不想实例化 object。执行此操作的函数是静态的,我看不到一种方法可以将 parms 类型与 object 类型相关联,从而使我可以进行静态调用。

(我也意识到这两个访问者结构可以在下面的代码中组合,但在真正的代码中,创建更长更复杂,我宁愿在每次重载时都没有它的副本。)

struct Type1Parms {};
struct Type2Parms {};
struct Type3Parms {};
...

struct TypeBase {};
struct Type1 : public TypeBase
{
    static bool ValidateParms(const Type1Parms&);
    Type1(const Type1Parms&);
};
struct Type2 : public TypeBase
{
    static bool ValidateParms(const Type2Parms&);
    Type2(const Type2Parms&);
};
struct Type3 : public TypeBase
{
    static bool ValidateParms(const Type3Parms&);
    Type3(const Type3Parms&);
};
...

struct ValidateParmsVisitor
{
    bool operator()(const Type1Parms& parms)
    {
        return Type1::ValidateParms(parms);
    }
    bool operator()(const Type2Parms& parms)
    {
        return Type2::ValidateParms(parms);
    }
    bool operator()(const Type3Parms& parms)
    {
        return Type3::ValidateParms(parms);
    }
...
};

using TypeParms = std::variant<Type1Parms, Type2Parms, Type3Parms, ...>;

struct CreateObjectVisitor
{
    std::unique_ptr<TypeBase> operator()(const Type1Parms& parms)
    {
        return std::make_unique<Type1>(parms);
    }
    std::unique_ptr<TypeBase> operator()(const Type2Parms& parms)
    {
        return std::make_unique<Type2>(parms);
    }
    std::unique_ptr<TypeBase> operator()(const Type3Parms& parms)
    {
        return std::make_unique<Type3>(parms);
    }
...
};

template<typename TParms>
std::unique_ptr<TypeBase> CreateType(const TParms& parms)
{
    unique_ptr<TypeBase> obj;
    if (visit(ValidateParmsVisitor{}, parms))
        obj = visit(CreateObjectVisitor{}, parms);
    return std::move(obj);
}

有没有办法建立这种关联,尤其是作为一种可以与静态成员函数调用一起使用的类型?

编辑:我应该解释一下,这是一个更大项目的一部分,还有许多其他设计标准决定了它的设计。

例如,这是一个客户端界面,其中 API 意味着尽可能简单。客户端只能看到(通过 header)parms 结构和一个函数,该函数采用 parms & returns 和包含上述 object 的 object。最初的设计确实有一个 parms 的基本结构,显然必须在 public header 中。但是,这意味着客户端可以自己从基础 class 继承并将其传递给 object 创建函数,或者从 acceptable 结构继承。为了避免段错误,这需要添加运行时检查以确保类型是 acceptable,这主要由散列设计处理——尽管并不是那么简单。当我删除散列设计时,我也丢失了这种类型验证方法,但我认识到这将被 variant<> 的编译时检查所取代,处理自定义结构(现在没有要检查的基础)。我还了解了处理继承问题的 final 关键字的 C++ 版本。

此外,虽然上面的代码没有显示,但 parms 结构包含多个成员,并且 ValidateParms() 函数实际上尝试验证值和组合是否有效。

您可以为协会创建特征:

template <typename T> struct from_param;

template <> struct from_param<Type1Parms> { using type = Type1; };
template <> struct from_param<Type2Parms> { using type = Type2; };
template <> struct from_param<Type3Parms> { using type = Type3; };

那么,你可以

using TypeParms = std::variant<Type1Parms, Type2Parms, Type3Parms>;

std::unique_ptr<TypeBase> CreateType(const TypeParms& parms)
{
    if (std::visit([](const auto& param){
            return from_param<std::decay_t<decltype(param)>>::type::ValidateParms(parms);
        }, parms))
    {
        return std::visit([](const auto& param) -> std::unique_ptr<TypeBase> {
            return std::make_unique<typename from_param<std::decay_t<decltype(param)>>::type>(parms);
        }, parms);
    }
    return nullptr;
}

Demo

或没有变体,如果您使用正确的类型调用:

template <typename T>
auto CreateType(const T& parms)
{
    if (from_param<T>::type::ValidateParms(parms))
    {
        return std::make_unique<typename from_param<T>::type>(parms);
    }
    return nullptr;
}

有一个很简单的方法,一组重载函数:

unique_ptr<TypeBase> CreateType(Type1Params const& params)
{
    return make_unique<Type1>(params);
}
unique_ptr<TypeBase> CreateType(Type2Params const& params)
{
    return make_unique<Type2>(params);
}
unique_ptr<TypeBase> CreateType(Type3Params const& params)
{
    return make_unique<Type3>(params);
}

备注:

  • 您可以添加另一个重载来捕获其他参数,然后 return null,但我认为编译时错误会更可取。
  • 您也可以使用模板函数和特化,但这种方式可能需要很少的输入来确保安全。