【问题标题】:Any way to define a "doubling" operator?有什么方法可以定义“加倍”运算符?
【发布时间】:2016-08-23 15:42:20
【问题描述】:

我有一个类,它表示一个只能采用整数或半整数值的量,因此在内部将其值存储为一个整数,该整数是该值的两倍。即:值 0.5 存储为 1,1 存储为 2,1.5 存储为 3,等等。我们将其称为 class A,存储值 int TwoA_

这会强制A 值正确地为整数或半整数。由于我经常需要将它们用作它们的真实值,因此我自然而然地对double 运算符进行了强制转换,该运算符仅返回0.5 * TwoA_

但是,我经常需要使用两倍的值。我认为将2 * a 编码(aA 类型的对象)直接返回存储在a 中的值TwoA_ 会很简洁。

理想情况下,我想创建一个仅在从左侧乘以 2 时才执行此操作的运算符。隐式转换将涵盖所有其他情况。

有没有办法在 C++ 中做到这一点?我想到了某种模板化的乘法运算符。

仅定义的简单解决方案

friend double operator*(int lhs, const A& rhs)
{ return (lhs == 2) ? rhs.TwoA_ : lhs * static_cast<double>(rhs); }

不是我要找的。 2 * a 保证是 int(但一般 an_int * a 不是),我想返回一个 int。

这是一个好奇的问题;如果您只想评论“为什么要这样做?” (或其中的一些变体),请闭嘴。

编辑:我的意思是左边乘以整数文字2(不是一个整数变量,它被测试是否有值 2)

【问题讨论】:

  • 抱歉,2 * a3 * a 不能有不同的类型。
  • @aschepler:是的,不同的类型只能使用模板参数,例如a.scaleby&lt;2&gt;() 可以具有与 a.scaleby&lt;3&gt;() 不同的类型
  • @aschepler,除非您提供理由,否则您的回答完全没有帮助。
  • @DanielGr:这是语言律师的原因,您想要从标准中引用?或者知道重载解析使用参数类型而不是参数值就足够了吗?
  • 请注意,您在浪费时间——double 可以精确地存储半整数而不会出现表示错误(直到您远远超出INT_MAX)并且如果您的转换对编译器可见,它将内联它并删除多余的乘法。

标签: c++ c++11 operator-overloading template-argument-deduction


【解决方案1】:

您的问题是您是否可以重载 * 运算符,使其根据参数之一的值返回不同的数据类型,简短的回答是:否。

长答案:不,但也许是的。

double operator*(const int lhs, const A& rhs);

该操作员必须在编译时知道lhs 是否为2,并且不能保证。这意味着必须在运行时决定如何处理lhs 是否为2 的情况。但是运算符返回的数据类型必须在编译时就知道...

double operator*(const some_type_1& lhs, const A& rhs);
int operator*(const some_type_2& lhs, const A& rhs);

这将为您提供两种不同的返回类型,但只有在编译时知道这些参数类型是什么。

template<int T>
struct special_number{};
//...
template<>
int operator*(const special_number<2>&, const A& rhs){
  return rhs.TwoA_;
}

template<int T>
double operator*(const special_number<T>&, const A& rhs){
  return (static_cast<double>(rhs) / 2.0) * T;
}

//usage
int answer1 = special_number<2> * my_a_object;
double answer2 = special_number<3> * my_a_object;

这样就可以了。

【讨论】:

  • 我在这里建议 std::integral_constant 而不是 special_number 会不会错?
  • 我想我的原始问题应该更清楚一点。我不想涵盖x * a 的情况,其中x 恰好有值2。我的意思是左边乘以文字2。我想可能使用template&lt;int N&gt; auto operator*(int N, A a) -&gt; typename std::enable_if&lt;N==2, int&gt;::type 之类的东西——我明白那是遗漏了一些东西,但认为它可能会以某种方式进行。
  • 不,您的N 必须在编译时已知,并且必须保证在编译时已知才能使其工作。传递给函数的参数可以在编译时知道,但对于运行时也可以是动态的。使用类似演示的东西(或 std::integral_constant,正如 John P 提到的那样)可以让您在编译时了解要使用哪个重载函数,但在我看来,在大多数情况下会有点过头了。
  • 另外请记住,编译器优化可能会在看到使用常量2 时完全减少原始帖子的运行时检查。我意识到这更像是一个概念性的“如果有的话怎么做”的问题,并且可以完成一般模式,但是根据我的经验,如果没有好的通用用例,它可能不值得,如果有是一个人为的用例,可能有更好、更具体的解决方案。
  • @Altainia,我希望在编译时知道它。但是,是的,涉及创建一个新结构来表示文字 2 的复杂建议是矫枉过正的——如果我要拥有 2 * a 以外的其他东西,为什么不添加一个方法 twice(a)?是的:这是一个纯粹的概念问题。
【解决方案2】:
struct two_t {
  constexpr two_t() {};
  template<class T>
  constexpr operator T()const{return 2;}
};
constexpr two_t two;

现在:

friend double operator*(two_t lhs, const A& rhs)
{ return rhs.TwoA_; }
friend double operator*(const A& lhs, two_t lhs)
{ return lhs.TwoA_; }

是你能做到的最好的。

表达式的类型不依赖于值,除非在常量0 可以转换为指针并可能导致不同的重载决议的特殊情况下。

当作为参数传递给* 时,不能利用这种特殊情况使2 变得特殊。

现在

constexpr std::ptrdiff_t from_digits(std::integer_sequence<char>) {
  return 0;
}
constexpr std::ptrdiff_t compile_time_pow( std::ptrdiff_t base, std::size_t exp ) {
  return exp ? base * compile_time_pow( base, exp-1 ) : std::ptrdiff_t(1);
}
template<char c0, char...cs>
constexpr std::ptrdiff_t from_digits(std::integer_sequence<char, c0, cs...>) {
  return
    compile_time_pow(10, sizeof...(cs))*(c0-'0')
    + from_digits( std::integer_sequence<char, cs...>{} );
}
template<char...cs>
constexpr auto operator"" _integer() {
  return std::integral_constant<std::ptrdiff_t, from_digits( std::integer_sequence<char, cs...>{} )>{};
}

现在2_integer 是值2 的编译时间std::ptrdiff_t,其值编码在其类型中。

因此2_integer * a 可以被覆盖以返回与3_integer * a2 * a 不同的类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多