【问题标题】:C++ template class operator overloading with the same signatures具有相同签名的 C++ 模板类运算符重载
【发布时间】:2019-06-04 05:49:36
【问题描述】:

一个简单的C++ OO问题重载模板和运算符重载:在下面的类中,我已经重载了两次索引运算符:

template<class A, class B>
class test
{
  A a1;
  B a2;
public:
  A& operator[](const B&);
  B& operator[](const A&);
};

现在,如果我用相同的类型名实例化这个模板类的一个对象:

test<int, int> obj;

调用索引运算符将导致错误,因为两个重载函数将具有相同的签名。

有没有办法解决这个问题?

对不起,如果这是一个基本问题。我还在学习!

【问题讨论】:

  • 您可以使用std::enable_if,并且仅在AB 类型不同时才启用第二次重载
  • 我认为这更像是一个设计/分析/需求问题,您需要退后一步来避免此类问题。是的,您可以通过“hacks”来缓解或解决代码本身的问题,但这种解决方法往往不太理想,并且会使代码更难理解和(更重要的是)维护。
  • 这将是typename std::enable_if&lt;!std::is_same_v&lt;A, B&gt;, B&gt;::type operator[](const A&amp;); IIRC。
  • 这是x y problem。你到底想做什么?
  • @MatthieuBrucher 为什么不把它作为一个答案呢?

标签: c++ class templates operator-overloading


【解决方案1】:

您可以添加部分专业化:

template<class A>
class test<A, A>
{
  A a1, a2;
public:
  A& operator[](const A&);
};

【讨论】:

  • 这在技术上是一种方法,但您可能必须复制该类所做的所有其他事情。仍然得到我的支持。
  • @MaxLanghof 通常可以通过继承一个共同的基础来避免代码重复。
【解决方案2】:

您可以通过将索引转换为明确用户想要什么的其他类型来避免这个问题并使代码更加健壮和富有表现力。用法是这样的:

bidirectional_map<int, int> myTest;
int& valueFor1 = myTest[Key{1}];
int& key1 = myTest[Value{valueFor1}];

这样实现的:

template<class TKey>
struct Key { const TKey& key; };

template<class TValue>
struct Value { const TValue& value; };

// Deduction guides (C++17), or use helper functions.
template<class TValue>
Value(const TValue&) -> Value<TValue>;
template<class TKey>
Key(const TKey&) -> Key<TKey>;

template<class TKey, class TValue>
class bidirectional_map
{
  TKey a1;   // Probably arrays
  TValue a2; // or so?
public:
  TValue & operator[](Key<TKey> keyTag) { const TKey & key = keyTag.key; /* ... */ }
  TKey & operator[](Value<TValue> valueTag) { const TValue& value = valueTag.value; /* ... */ }
};

现在,KeyValue 是流行的名称,因此让它们被这些辅助功能“占用”并不是最好的。此外,这只是一个非常理论化的练习,因为成员函数当然更适合这项任务:

template<class TKey, class TValue>
class bidirectional_map
{
  TKey a1;   // Probably arrays
  TValue a2; // or so?
public:
  TValue& getValueForKey(const TKey& key) { /* ... */ }
  TKey& getKeyForValue(const TValue& value) { /* ... */ }
};

【讨论】:

    【解决方案3】:

    在 C++2a 中,在某些情况下,您可能会使用 requires 来“丢弃”该函数:

    template<class A, class B>
    class test
    {
        A a1;
        B a2;
    public:
        A& operator[](const B&);
        B& operator[](const A&) requires (!std::is_same<A, B>::value);
    };
    

    Demo

    【讨论】:

      【解决方案4】:

      这是一个使用 if constexpr 的示例解决方案,需要 C++17:

      #include <type_traits>
      #include <cassert>
      #include <string>
      
      template <class A, class B> 
      class test
      {
        A a1_;
        B b1_;
      
      public:
      
          template<typename T> 
          T& operator[](const T& t)
          {
              constexpr bool AequalsB = std::is_same<A,B>(); 
              constexpr bool TequalsA = std::is_same<T,A>();
      
              if constexpr (AequalsB)
              {
                  if constexpr (TequalsA) 
                      return a1_;  // Can also be b1_, same types;
      
                  static_assert(TequalsA, "If A=B, then T=A=B, otherwise type T is not available.");
              }
      
              if constexpr (! AequalsB)
              {
                  constexpr bool TequalsB = std::is_same<T,B>();
      
                  if constexpr (TequalsA)
                      return a1_; 
      
                  if constexpr (TequalsB)
                      return b1_; 
      
                  static_assert((TequalsA || TequalsB), "If A!=B, then T=A || T=B, otherwise type T is not available.");
              }
          }
      
      };
      
      using namespace std;
      
      int main()
      {
          int x = 0;  
          double y = 3.14; 
          string s = "whatever"; 
      
          test<int, int> o; 
          o[x]; 
          //o[y]; // Fails, as expected.
          //o[s]; // Fails, as expected
      
          test<double, int> t; 
          t[x]; 
          t[y]; 
          //t[s]; // Fails, as expected.
      
          return 0; 
      
      };
      

      【讨论】:

      • 注意 OP 返回其他类型。使用decltype(auto) 可以解决这个问题。
      • @Jarod42:我不明白你的评论,你的意思是decltype(auto)关于成员函数模板的返回类型?为什么这是必要的,constexpr部分与模板参数推导不会确保正确的T在返回(T&amp;)中被替换?
      • 首先,这里没有ADL。其次,OP 的代码允许test&lt;std::string, int&gt; t; std::string s = t[42];,而您的代码需要int i = t[42];
      • 另外,你松了转换:test&lt;std::string, int&gt; t; t["c-string"];对你无效(需要t[std::string("c-string")])。
      • 这两个问题都可以修复,decltype(auto) 用于第一个(并修复实现以返回 OP 值 ;-)),第二个更棘手,但 std::is_samestd::is_convertible 可能会工作,但不确定它会比替代方案提供“更好”的代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-26
      • 2019-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多