允许对具有相同基础结构的类型进行隐式转换

时间:2018-04-18 13:49:07

标签: c++ c++11 data-structures c++14 c++17

我的代码中有几个结构都有

  • 一个具有相同类型的成员,例如std::paircv::Point2d
  • 不同的功能,例如每个结构中命名不同的getter / setter

示例:

struct CartesianCoordinates {
    std::pair<double, double> xy;

    void x(double val) { xy.first = val; }
    void y(double val) { xy.second = val; }
    double x() const { return xy.first; }
    double y() const { return xy.second; }
};

struct GeographicCoordinates {
    std::pair<double, double> xy;

    void longtitude(double val) { xy.first = val; }
    void lattitude(double val) { xy.second = val; }
    double longtitude() const { return xy.first; }
    double lattitude() const { return xy.second; }
};

现在我希望能够在隐式之间进行转换,如下所示:

CartesianCoordinates returnCartesian()
{
    CartesianCoordinates c;
    c.x(5);
    c.y(-13);
    return c;
}

void getGeographicCoordinates(const GeographicCoordinates& c) {
    std::cout << c.longtitude() << '\n';
}

int main()
{
    getGeographicCoordinates(returnCartesian());
}

如果没有

,有没有办法实现这一目标
  • 定义强制转换运算符(我不想在每个结构中定义它们,1:1),
  • 为这些结构提供基类(如果我不想定义任何基类)?

实际上reinterpret_cast /指针应该可以工作,但是存在我不知道的任何现代C ++机制,这可以帮助我用最少的结构实现开销来解决它,只是使用的事实所有结构都具有相同的结构

3 个答案:

答案 0 :(得分:4)

好吧,只要你不想转换数据成员中的数据,并且只要成员总是命名相同的东西,就可以使用模板转换运算符。然后使用SFINAE将模板限制为仅具有与该类的指定成员类型相同的命名成员的类型。添加

template <typename T, std::enable_if_t<std::is_same_v<std::pair<double, double>, decltype(std::declval<T&>().xy)>>* = nullptr>
operator T() { return T{xy}; }

两个类允许它们相互转换并允许

int main()
{
    getGeographicCoordinates(returnCartesian());
}

运行(live example

如果这些类不使用相同的成员变量名,那么你可以让它们全部表面一个名为相同的typedef,它是数据成员的类型并使用它。那看起来像是

template <typename T, std::enable_if_t<std::is_same_v<my_variable_typedef, typename T::my_variable_typedef>>* = nullptr>
operator T() { return T{xy}; }

答案 1 :(得分:0)

这可以通过定义成员的访问接口来解决。

我建议您重复使用get惯用法,并为希望此行为处理的所有结构定义模板化(在索引上)get自由函数。

然后,您必须以与模板类似的方式定义应对所有这些类型起作用的函数,并通过get接口访问成员。

答案 2 :(得分:0)

不,没有大量的结构模板。

  

实际上reinterpret_cast / pointers应该可以工作

实际上并非如此。您可以使用对象是标准布局结构中的第一个元素的事实从struct到pair重新解释,但是如果该结构类型实际存在,则只能从pair重新解释为struct

如果结构类型的对象不在那里,则从对到结构的重新解释是未定义的行为。

这是尝试在中使用最少的样板文件执行此操作,同时保持通用。我们从一个空的CRTP空父类继承,以注入一些朋友和转换运算符。此外,我们必须添加一些代码,将每个成员公开给CRTP父级。

template<class A, class B>
struct tie_same;

template<class A, class B>
using tie_check = typename tie_same<A,B>::type;

template<class D>
struct structured {
  friend auto as_tie( structured<D>& s ) {
    auto& self = static_cast<D&>(s);
    auto members = get_members(self);
    return std::apply( [&self](auto...pm) {
      return std::tie( (self.*pm)... );
    }, members );
  }
  friend auto as_tie( structured<D> const& s ) {
    auto& self = static_cast<D const&>(s);
    auto members = get_members(self);
    return std::apply( [&self](auto...pm) {
      return std::tie( (self.*pm)... );
    }, members );
  }

  template<class T,
    std::enable_if_t<
      tie_check<T,D>{},
      bool
    > = true
  >
  operator T() const& {
    T tmp;
    as_tie(tmp) = as_tie( *this );
    return tmp;
  }
};

template<class A, class B>
struct tie_compatible {
  template<class X>
  using tie_type = decltype( as_tie( std::declval<X>() ) );
  using type = std::is_same< tie_type<A&>, tie_type<B&> >;
};

有点棘手,但能完成这项工作。

要在X类型中支持此系统,您需要:

  1. X继承structured<X>
  2. 实现auto members(X const& x),返回指向您关注的指针成员的元组。
  3. 为您的目标类型设置一个默认构造函数,并且可以通过赋值进行构建。
  4. 以下是使用此功能增强的类型:

    struct CartesianCoordinates: structured<CartesianCoordinates> {
      std::pair<double, double> xy;
    
      friend auto get_members( CartesianCoordinates const& self ) {
        return std::make_tuple( &CartesianCoordinates::xy );
      }
    
      void x(double val) { xy.first = val; }
      void y(double val) { xy.second = val; }
      double x() const { return xy.first; }
      double y() const { return xy.second; }
    };
    
    struct GeographicCoordinates: structured<GeographicCoordinates> {
      std::pair<double, double> xy;
    
      friend auto get_members( GeographicCoordinates const& self ) {
        return std::make_tuple( &GeographicCoordinates::xy );
      }
    
      void longtitude(double val) { xy.first = val; }
      void lattitude(double val) { xy.second = val; }
      double longtitude() const { return xy.first; }
      double lattitude() const { return xy.second; }
    };
    

    中,只需实施您自己的notstd::apply

    中,由于缺少退货类型扣除而令人讨厌。

    #define RETURNS(...) \
       noexecpt(noexcept(__VA_ARGS__)) \
       -> decltype(__VA_ARGS__) \
       { return __VA_ARGS__; }
    

    RETURNS可以稍微改变这种痛苦。

    auto foo()
      RETURNS( 1+2 )
    

    替换

    auto foo() {
      return 1+2;
    }
    

    有时会奏效。 (仍有重要的差异,但你能做些什么)