使用std::variant和std::visit实现联合模板

3

我正在使用一个模板库,由于在我的代码中一个模板参数可以取有限范围内的值,所以在建议下决定使用std::variant,并在其中声明我可能需要的所有对象:

std::variant<TemplateClass<1>, TemplateClass<2>, ..., TemplateClass<5>>

我从未使用过这个实用程序。
要访问TemplateClass的方法,我必须使用`std::visit`,但有时它有效,有时又不起作用,会报告`std::variant <....>中没有成员函数XXX`或`“函数模板特化的实例化…”`(我甚至不知道问题出在哪里)。
具体来说,我正在使用`Eigen::Tensor`库,当我调用`rank()`、`dimension(n)`等方法时,它可以工作,而对于`dimensions()`和`setRandom()`之类的方法则无法工作。
以下是我的实现草稿。
std::variant<Eigen::Tensor<double, 1>, Eigen::Tensor<double, 2>, /* ... */> makeTensor(
    int i, const std::initializer_list<int> dims) {
  switch (i) {
    case 1: {
      Eigen::Tensor<double, 1> T1;
      T1.resize(dims);
      return T1;
    }

    case 2: {
      Eigen::Tensor<double, 2> T2;
      T2.resize(dims);
      return T2;
    }

      /* ... */
  }
}

int main() {
  auto myTensor{makeTensor(2, {4, 5})};  // Tensor 2D 4x5

  // Working methods
  auto rnk = std::visit([](const auto &tensor) { return tensor.rank(); }, myTensor);

  auto dim1 = std::visit([](const auto &tensor) { return tensor.dimension(0); }, myTensor);

  // Not working methods

  auto dimsTens =
      std::visit([](const auto &tensor) { return tensor.dimensions(); }, myTensor);  // 5 times same error saying
  //'In instantiation of function template specialization 'std::visit<(lambda at
  /// home/virginie/Desktop/Project/main.cpp:62:33),
  // std::variant<Eigen::Tensor<double, 1, 0, long>, Eigen::Tensor<double, 2, 0, long>, Eigen::Tensor<double, 3, 0,
  // long>, Eigen::Tensor<double, 4, 0, long>, Eigen::Tensor<double, 5, 0, long>> &>''

  std::visit([&myTensor]() { myTensor.setRandom(); });  // 'No member setRandom() in std::variant<...>'
}

我是否错误地使用了 std::visit

---- 编辑 ----

在 @florestan 的建议下,我解决了与 dimensions() 相关的问题,但是对于 setRandom,我得到了以下信息:

从 /..../ main.cpp 包含的文件 enter image description here required from here enter image description here


1
假设所有变量成员都有返回相同类型的rank()dimension(int)成员,这似乎是正确的。如果您遇到编译错误,则意味着其中某些成员没有该类成员,或者它们返回不同的类型。 - Sam Varshavchik
1
这是因为大多数情况下,每个 setRandom 都会返回一个 void。在 C++ 中,所有对象都必须具有显式类型,因此 auto rank= 必须具有某些特定的推断类型。但是,如果访问者根据变量中的值返回不同的类型,则 auto rank 的实际类型必须在运行时变化。C++ 不是这样工作的。很可能是因为 auto rnk 本身必须是所有可能返回类型的 variant,并由访问者直接分配,而不是每个访问者都返回 void - Sam Varshavchik
你真的需要 std::variant 的原因是什么? - florestan
1
参考此处的 dimensions() 函数:https://gitlab.com/libeigen/eigen/-/blob/3.3.7/unsupported/Eigen/CXX11/src/Tensor/Tensor.h#L102,以及这里的 setRandom() 函数:https://gitlab.com/libeigen/eigen/-/blob/3.3.7/unsupported/Eigen/CXX11/src/Tensor/TensorBase.h#L849-856。建议使用返回类型 DSizes,它有一个用于秩的模板参数。您可以将其封装为 Array<IndexType, Dynamic, 1>(例如 ArrayXi)。 - jdehesa
1
对于setRandom(),在您发布的代码中,您正在尝试在变量对象中调用该方法,您应该像其他情况一样通过访问者来完成,例如std::visit([](const auto &tensor) { tensor.setRandom(); }, myTensor); - jdehesa
显示剩余4条评论
1个回答

3

您需要为所有可能的替代方案返回相同的类型。在 dimension 的情况下,您需要将数组元素复制到一个向量中,例如。

可以使用以下方法:

 auto dimsTens=std::visit(
    [](const auto &tensor) {
       auto dims = tensor.dimensions();
       return std::vector<int>(dims.begin(), dims.end());
    }, myTensor);

第二个错误是因为你没有正确调用 std::visit。它需要两个参数,第一个参数是函数,第二个参数是要访问的 variant。以下方法应该可以工作。
std::visit([](auto& t){ t.setRandom();}, myTensor); 

实时代码: https://godbolt.org/z/vq4PYo


我正在实现一个方法,但在使用std::visit时仍然遇到问题。我认为我还没有完全理解这个工具,而且错误信息难以理解,无法帮助我理解出错的原因。该方法应返回对象张量的副本,因此我面临着根据变体具有不同返回类型的情况。我该如何处理呢? 这是我的实现链接。 https://godbolt.org/z/e37Mqd - Virginie
你的代码存在几个问题。请在创建示例时小心,例如 MyTensormyTensor 不同!经过一番挖掘,似乎问题在于 tensor.shuffle(...) 并不返回一个张量,而是一个张量表达式,它不能隐式转换为张量。我对 Eigen 的了解还不够深入,无法告诉你如何修复这个问题。另一件事是,你不能从 std::visit 返回不同的类型,因此需要在 lambda 中捕获结果张量变体(通过引用),并在 lambda 中进行赋值。 - florestan
但说实话,你的整个方法似乎有点不符惯用法,也许你应该思考一下是否真的需要那些变量。也许你可以使用rank-5张量,并将不必要的维度变为单数?或者你可以想办法分解运行时依赖关系? - florestan
我会考虑的,谢谢。再次感谢。 - Virginie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接