【问题标题】:Operator overloading on an enum nested in a class嵌套在类中的枚举上的运算符重载
【发布时间】:2020-11-13 01:26:51
【问题描述】:

问题

给定以下代码:

template <typename T>
struct dummy {
  enum enumenum { a = 1, b = 2, c = 4 };
};

int main() { 
    // I want this line to expands as : 
    // dummy<double>::enumenum a = operator~(dummy<double>::a);
    auto a = ~dummy<double>::a;
}

如何在 enumenum 上重载运算符? 我正在使用标准 C++14。

我尝试了什么

一个简单的实现:

template <typename T>
typename dummy<T>::enumenum 
operator~(typename dummy<T>::enumenum a) {
  return static_cast<typename dummy<T>::enumenum>(operator~(a));
}

不幸的是,有问题的行扩展为:

int a = ~static_cast<int>(dummy<double>::a);

这意味着未使用该运算符(这是默认行为)。 是不是因为 ADL 在 struct 命名空间中找不到正确的operator~()(这甚至是一件事吗?)?

然后我尝试了:(注意friend

template <typename T>
struct dummy {
  enum enumenum { a, b, c };
  
  friend enumenum operator~(enumenum a) { 
    return static_cast<enumenum>(~a);
  }
};

这实际上工作并扩展为:

template <>
struct dummy<double> {
  enum enumenum {
    a = static_cast<unsigned int>(1),
    b = static_cast<unsigned int>(2),
    c = static_cast<unsigned int>(4)
  };

  friend inline dummy<double>::enumenum operator~(dummy<double>::enumenum a) {
    return static_cast<dummy<double>::enumenum>(operator~(a));
  }
};

int main()
{
  dummy<double>::enumenum a = operator~(dummy<double>::a);
}

这是我想要的行为。除了,如果我不想在类主体中定义运算符怎么办。

所以我尝试了:

template <typename T>
struct dummy {
  enum enumenum { a = 1, b = 2, c = 4 };
  
  // if inline : inline function 'operator~' is not defined [-Wundefined-inline]
  // and adding inline to the template below does not help
  friend enumenum operator~(enumenum a);
};

template <typename T>
typename dummy<T>::enumenum 
operator~(typename dummy<T>::enumenum a) {
  return static_cast<typename dummy<T>::enumenum>(~a);
}

int main() { 
    auto a = ~dummy<double>::a; 
}

上面的代码展开为:

template<>
struct dummy<double>
{
  enum enumenum
  {
    a = static_cast<unsigned int>(1), 
    b = static_cast<unsigned int>(2), 
    c = static_cast<unsigned int>(4)
  };
  
  friend dummy<double>::enumenum operator~(dummy<double>::enumenum a);
};

int main()
{
  dummy<double>::enumenum a = operator~(dummy<double>::a);
}

这可以编译,但链接! 编辑:我相信它不会链接,因为模板没有被实例化,因此在链接时失败(类似于上面的幼稚实现)。

结论

即使我以某种方式找到了实现我想要的方法,但如果我不想在类定义中定义运算符

提前致谢。

【问题讨论】:

  • 请务必在问题中包含错误消息。上一个版本在这里编译(和链接)很好:godbolt.org/z/oW7TY5 您是否将运算符的定义放在源文件中?另外我不明白“此代码扩展为”是什么意思
  • 我也在使用编译器资源管理器:godbolt.org/z/87a149,我收到以下错误:未定义对 `operator~(dummy::enumenum)' clang-10 的引用:错误:链接器命令退出代码失败 1. 编辑:我相信您没有收到链接错误的原因是您给我的链接 (godbolt.org/z/oW7TY5) 没有使用“编译为二进制”选项或“运行编译后的输出”选项”。因此它不会运行链接器。
  • 如果我不想在类主体中定义运算符怎么办 - 在任何情况下,这似乎都是最优雅的解决方案。为什么不? (经过多次修补后无法解决您的问题)。
  • 如果是风格问题,您始终可以调用友元定义中其他地方定义的方法(尽管您可能需要添加一些前向声明)。附:如果您使用template &lt;typename T&gt; T operator~(T a)naive 实现可以工作,但我找不到检查T 是否属于dummy&lt;U&gt;::enumenum 类型的一些U

标签: c++ templates operator-overloading c++14 friend-function


【解决方案1】:

编译,但不链接!

编译但不链接,因为您声明了一个非模板运算符(它在模板结构内但不是模板函数)

friend enumenum operator~(enumenum a);

然后你定义了一个模板

template <typename T>
typename dummy<T>::enumenum 
operator~(typename dummy<T>::enumenum a) {
  return static_cast<typename dummy<T>::enumenum>(~a);
}

并且模板定义不能匹配非模板声明。

您可以尝试将函数声明为模板函数

template <typename T>
struct dummy
 {
   enum enumenum { a = 1, b = 2, c = 4 };

   template <typename U>
   friend typename dummy<U>::enumenum
      operator~ (typename dummy<U>::enumenum const & a);
 };

template <typename T>
typename dummy<T>::enumenum 
      operator~ (typename dummy<T>::enumenum const & a)
 { return static_cast<typename dummy<T>::enumenum>(~a); }

int main ()
 { 
   auto a = ~dummy<double>::a; 
 }

但是,不幸的是,这个编译但是,调用~ 操作符如下

 ~dummy<double>::a;

没有调用模板函数,因为模板参数T 无法推断,因为它位于未推断上下文中(在最后一个:: 之前),正如 Benny K 在评论中指出的那样。

因此,模板operator~()dummy&lt;double&gt;::a 被转换为int,并应用int~ 运算符(类型结果为int)。

你可以通过显式调用函数operator~()来验证这一点

 auto a = operator~(dummy<double>::a); 

你应该得到一个“no matching function for call to 'operator~'”错误(附注“注意:候选模板被忽略:无法推断模板参数'T'”)或类似的东西。

为了使这个解决方案有效,你必须说明类的类型,以避免模板推导

 auto a = operator~<double>(dummy<double>::a); 

现在,您可以验证 adummy&lt;double&gt;::enumenum

 static_assert( std::is_same<decltype(a), dummy<double>::enumenum>::value, "!" );

但是,显然,这不是一个令人满意的解决方案(而且非常危险,如果您忘记避免简单地使用 ~)。

否则你可以将操作符定义为非模板

template <typename T>
struct dummy
 {
   enum enumenum { a = 1, b = 2, c = 4 };

   friend enumenum operator~ (enumenum const & a);
 };

dummy<double>::enumenum 
      operator~(dummy<double>::enumenum const & a)
 { return static_cast<dummy<double>::enumenum>(~a); }

int main ()
 { 
   auto a = ~dummy<double>::a; 
 }

但您必须为每个 dummy&lt;T&gt; 类型定义不同的运算符。

恕我直言,最简单、安全和优雅的解决方案是您的工作解决方案:在结构中声明/定义运算符。

【讨论】:

  • 您好,感谢您的回答。尽管使用模板化朋友声明的想法听起来是正确的,但它似乎不起作用。事实上,我相信auto a = ~dummy&lt;double&gt;::a; 会扩展为int a = ~static_cast&lt;int&gt;(dummy&lt;double&gt;::a);。如果您将auto 替换为dummy&lt;double&gt;,则可以检查这一点,这将无法编译(无法从'int' 转换为'dummy | 使用clang 10)。您的模板化朋友声明版本示例,auto 除外:godbolt.org/z/M57W4h
  • 第一个解决方案无法工作,因为签名不同。但是,即使它们不是,它似乎也不起作用:godbolt.org/z/zj66xb
  • @EtienneM - 你是对的......好吧,a 的预期值是dummy&lt;double&gt;::enumenum,而不是dummy&lt;double&gt;,但你是对的:你得到int。我忘记了模板扣除。答案已修改。
  • @BennyK - 你是对的:不同的签名(更正),无论如何,不​​起作用。你比我之前看到的问题:模板推导。答案已修改。
  • @max66 对于评论中的错误,我深表歉意,遗憾的是我无法编辑它...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 2011-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多