Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading the typecast operator for std::tuple

Tags:

c++

c++17

The preamble: Hey, let's say I have various representations of data that I want to convert seamlessly in-between in an any-to-any way. The representations are out of my control. My practical example is the object orientation in 3D: we have Quaternions, Euler angles, Angle-Axis, and Rotational matrices, everything in various classes from different libraries (which I have to use). For this, I built a proxy class which stores the value in one particular representation and can convert into it by overloaded constructors and out of it by overloaded typecast operators, just like this:

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

The problem: So far, so good until I want to overload the typecast to std::tuple, which is handy, e.g., when returning the yaw, pitch and roll angles, which would look like this:

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

The class can be compiled, but the assignments to auto [a, b, c] and std::tie(a, b, c) do not work. A workaround can be made in the form of a dedicated function, which returns the tuple. Or by creating a custom class just for storing the three doubles. These work just fine, but it is not that seamless anymore.

I know that functions can not be overloaded by their return type. That is the reason why I created this proxy class. But is there any other way how the tuple could be returned? Even if it is for just a single variant of the tuple? Or should I approach this problem differently?

I prepared a minimum (non)working example in the theme of simpler number conversion:

#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;
};

Makefile:

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

all: main

Expected output:

intval: 3, fracval: 3
decimal and fractional parts: 3, 0.14
decimal and fractional parts: 1, 0.618
like image 789
Tomáš Báča Avatar asked Nov 29 '25 00:11

Tomáš Báča


1 Answers

Jarod42s hint to binding_a_tuple-like_type made me come up with the following.

I basically make your NumberConvertor act like a tuple.

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

For convenience an alias template can be used:

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

Using that, we can provide a get method:

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_;}

The specializations aren't really needed here, but I suppose for the real scenario this is not the case.

Finally we provide specializations for std::tuple_size and 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> 
{};

Now this will work:

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

Complete example

like image 162
463035818_is_not_a_number Avatar answered Nov 30 '25 14:11

463035818_is_not_a_number



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!