首页 > 解决方案 > 重载 std::tuple 的类型转换运算符

问题描述

序言:嘿,假设我有各种数据表示,我想以任意到任意的方式在它们之间无缝转换。这些陈述是我无法控制的。我的实际示例是 3D 中的面向对象:我们有四元数、欧拉角、角轴和旋转矩阵,以及来自不同库(我必须使用)的各种类中的所有内容。为此,我构建了一个代理类,它将值存储在一个特定的表示中,并且可以通过重载的构造函数将其转换为它,并通过重载的类型转换运算符将其转换出来,就像这样:

Eigen::Quaterniond eig_quaternion = AttitudeConvertor(roll, pitch, yaw);
tf2::Quaternion    tf2_quaternion = AttitudeConvertor(eig_quaternion);

问题:到目前为止,一切都很好,直到我想将类型转换重载到std::tuple,这很方便,例如,在返回yawpitchroll角度时,看起来像这样:

auto [roll2, pitch2, yaw2] = AttitudeConvertor(tf2_quaternion);

该类可以编译,但分配给auto [a, b, c]并且std::tie(a, b, c)不起作用。可以通过返回元组的专用函数的形式进行解决方法。或者通过创建一个自定义类来存储三个双打。这些工作正常,但不再那么无缝了。

我知道函数的返回类型不能重载。这就是我创建这个代理类的原因。但是还有其他方法可以返回元组吗?即使它只是元组的一个变体?还是我应该以不同的方式解决这个问题?

我准备了一个以简单数字转换为主题的最小(非)工作示例:

#include <iostream>
#include <math.h>
#include <tuple>

using namespace std;

class NumberConvertor {

public:
  // | ---------------------- constructors ---------------------- |

  NumberConvertor(const int& in) {
    value_ = double(in);
  }

  NumberConvertor(const double& in) : value_(in){};

  // | ------------------- typecast operators ------------------- |

  operator int() const {
    return int(value_);
  }

  operator double() const {
    return value_;
  }

  // return the integer and the fractional part
  operator std::tuple<int, double>() const {

    int    int_part  = floor(value_);
    double frac_part = fmod(value_, int_part);
    return std::tuple(int_part, frac_part);
  }

  // | ------------------------ functions ----------------------- |

  // the workaround
  std::tuple<int, double> getIntFrac(void) const {

    int    int_part  = floor(value_);
    double frac_part = fmod(value_, int_part);
    return std::tuple(int_part, frac_part);
  }

private:
  double value_;  // the internally stored value in the 'universal representation'
};

int main(int argc, char** argv) {

  // this works just fine
  int    intval  = NumberConvertor(3.14);
  double fracval = NumberConvertor(intval);
  cout << "intval: " << intval << ", fracval: " << fracval << endl;

  // this does not compile
  // auto [int_part, frac_part] = NumberConvertor(3.14);

  // neither does this
  // int a;
  // double b;
  // std::tie(a, b) = NumberConvertor(3.14);

  // the workaround
  auto [int_part2, frac_part2] = NumberConvertor(3.14).getIntFrac();
  cout << "decimal and fractional parts: " << int_part2 << ", " << frac_part2 << endl;

  std::tie(int_part2, frac_part2) = NumberConvertor(1.618).getIntFrac();
  cout << "decimal and fractional parts: " << int_part2 << ", " << frac_part2 << endl;

  return 0;
};

生成文件:

main: main.cpp
    g++ -std=c++17 main.cpp -o main

all: main

预期输出:

intval: 3, fracval: 3
decimal and fractional parts: 3, 0.14
decimal and fractional parts: 1, 0.618

标签: c++c++17

解决方案


Jarod42s 对binding_a_tuple-like_type 的提示让我想出了以下内容。

我基本上让你的NumberConvertor行为像一个元组。

using as_tuple_type = std::tuple<int,double>;

为方便起见,可以使用别名模板:

template <size_t i>
using nth_type = typename std::tuple_element_t<i,as_tuple_type>;

使用它,我们可以提供一个get方法:

struct NumberConvertor {
  NumberConvertor(const int& in) : value_(in) {}
  NumberConvertor(const double& in) : value_(in) {};
  template <size_t i> nth_type<i> get();
private:
  double value_;
};

template <> nth_type<0> NumberConvertor::get<0>() { return value_;}
template <> nth_type<1> NumberConvertor::get<1>() { return value_;}

这里并不真正需要专业化,但我想对于实际情况并非如此。

std::tuple_size最后,我们为和提供专业化std::tuple_element

template <> 
struct std::tuple_size<NumberConvertor> : std::tuple_size<as_tuple_type> 
{};
template <size_t i> 
struct std::tuple_element<i,NumberConvertor> : std::tuple_element<i,as_tuple_type> 
{};

现在这将起作用:

int main(int argc, char** argv) {
    auto [int_part, frac_part] = NumberConvertor(3.14);
    std::cout << int_part << " " << frac_part;
};

完整示例


推荐阅读