Перегрузка оператора typecast для std :: tuple - PullRequest
3 голосов
/ 29 марта 2020

Преамбула : Эй, скажем, у меня есть различные представления данных, которые я хочу беспрепятственно преобразовывать между ними любым способом. Представления вне моего контроля. Мой практический пример - объектная ориентация в 3D: у нас есть кватернионы, углы Эйлера, углы-оси и матрицы вращения, все в разных классах из разных библиотек (которые я должен использовать). Для этого я создал прокси-класс, который хранит значение в одном конкретном представлении и может преобразовать в него с помощью перегруженных конструкторов , а из - с помощью перегруженных операторов преобразования типов , например:

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

Проблема : Пока все хорошо, пока я не захочу перегрузить преобразование типов в std :: tuple , что удобно, например, при возврате yaw , pitch и roll angles, которые будут выглядеть следующим образом:

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

Makefile:

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

Ответы [ 3 ]

2 голосов
/ 29 марта 2020

Если бы я мог, я бы просто добавил это как комментарий к ответу от idclev .

Согласно совету Нико в http://www.cppstd17.com, Следует избегать версии функции-члена для предоставления доступа.

Кроме того, нет необходимости создавать дополнительные std::tuple экземпляры, когда в этом нет необходимости.

Вам нужно три вещи, чтобы заставить класс действовать как кортеж: tuple_size, tuple_element и get.

template <>
struct std::tuple_size<NumberConvertor>
{
    static constexpr int value = 2;
};
template <>
struct std::tuple_element<0, NumberConvertor>
{
    using type = int;
};
template <>
struct std::tuple_element<1, NumberConvertor>
{
    using type = double;
};

template <std::size_t I>
constexpr auto
get(NumberConvertor const &x)
{
    static_assert(I <= 1);
    if constexpr (I == 0) {
        return static_cast<int>(x);
    } else if constexpr (I == 1) {
        return static_cast<double>(x);
    }
}

Обратите внимание, что это дает доступ только для чтения, но в данном случае это именно то, что требуется.

2 голосов
/ 29 марта 2020

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

Полный пример

0 голосов
/ 29 марта 2020

Спасибо всем за огромную помощь! А именно, к idclev-463035818 за его ответ , который привел меня к завершению рабочего решения. Спасибо также Jody Hagins, чей ответ приводит к еще более элегантному решению. Однако оба решения работают только для назначения auto [a, b] =. К счастью, здесь Я нашел способ заставить std::tie(a, b) = работать:

  operator tuple<int&, double&>() {

    temp_int_    = floor(value_);
    temp_double_ = fmod(value_, temp_int_);

    return tuple<int&, double&>{temp_int_, temp_double_};
  }

Оператор определен как кортеж ссылок. Оператор return выглядит немного иначе, чем я пытался ранее. Но самое главное, что значения передаются правильно, только если переменные temp_int_ и temp_double_ являются членами класса. Я не знаю, как это работает, но я рад, что это работает.

Вот моя версия текущего минимального рабочего примера:

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

  operator tuple<int&, double&>() {

    temp_int_    = floor(value_);
    temp_double_ = fmod(value_, temp_int_);

    return tuple<int&, double&>{temp_int_, temp_double_};
  }

  template <std::size_t I>
  constexpr auto get() {

    static_assert(I <= 1);

    if constexpr (I == 0) {
      return static_cast<int>(floor(value_));
    } else if constexpr (I == 1) {
      return static_cast<double>(fmod(value_, floor(value_)));
    }
  }

private:
  int    temp_int_;     // this is here for tieing the returned tuple
  double temp_double_;  // this is here for tieing the returned tuple
  double value_;        // the internally stored value in the 'universal representation'
};

template <>
struct std::tuple_size<NumberConvertor>
{ static constexpr int value = 2; };

template <>
struct std::tuple_element<0, NumberConvertor>
{ using type = int; };

template <>
struct std::tuple_element<1, NumberConvertor>
{ using type = double; };

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;

  auto [int_part, frac_part] = NumberConvertor(3.14);
  cout << "decimal and fractional parts: " << int_part << ", " << frac_part << endl;

  std::tie(int_part, frac_part) = NumberConvertor(3.14);
  cout << "decimal and fractional parts: " << int_part << ", " << frac_part << endl;

  return 0;
};
...