重载[]运算符以返回变量类型

时间:2019-01-07 12:12:38

标签: c++ operator-overloading variant

编辑:由于有了答案,我得以解决代码中的所有问题。我在这里发布解决方案:将来可能对某人有用。特别地,使用代理类的建议非常有用!该示例并未考虑所有情况,但在变量中添加其他类型应该很简单!

我正在编写一个C ++(C11-Linux)自定义类,其行为类似于无序映射 {key,value} 。我想重载 [] 运算符,以便可以使用与无序映射相同的语法的类: object [key] 将返回 value < / em>。

问题是我需要 object [key] 返回一个变量类型。我可以在内部将 value 存储为字符串或结构,但是当我使用 object [key] 检索它时,我需要返回的值是 int float string ,具体取决于运行时确定的某些内部条件

这就是为什么我在考虑使用 boost :: variant库 ...的原因,但是我对其他建议持开放态度。唯一的限制是 test 类(在示例中)必须编译为共享库.so,并且代码必须与C11兼容(我的意思是可以由GNU g ++ 4.8.5编译)。 / p>

我写了一个简单的示例来说明我想要什么样的行为。该示例并不意味着任何含义。这只是为了说明我遇到的那种错误。我正在编写的真实类具有不同的结构,但是bool :: variant和operator []重载的用法是相同的。

test.cpp

#include <boost/variant.hpp>

typedef boost::variant<int, float> test_t;

class Test
{
  int i ;
  float f;
  void set(int randomint, test_t tmp){
    if ( randomint == 0 ) i = boost::get<int>(tmp);
    else f = boost::get<float>(tmp);
  }
  test_t get(int randomint){
    if ( randomint == 0 ) return i;
    else return f;
  }

  struct IntOrFloat {
    int randomint;
    Test *proxy;
    explicit operator int () const
    { return boost::get<int>(proxy->get(randomint)); }
    void operator= (int tmp)
    { proxy->set(randomint, tmp); }
    explicit operator float () const
    { return boost::get<float>(proxy->get(randomint)); }
    void operator= (float tmp)
    { proxy->set(randomint, tmp); }
  };

public:
  IntOrFloat operator [](int randomint)
  { return IntOrFloat{randomint, this}; }

  const IntOrFloat operator [](int randomint) const
  { return IntOrFloat{randomint, (Test *) this}; }
};

main.cpp

#include <iostream>
#include <boost/variant.hpp>
#include "test.cpp"

#define INTEGER 0
#define FLOAT 1

int main (void) {
  Test test;
  int i = 3;
  float f = 3.14;
  test[INTEGER] = i;
  test[FLOAT] = f;
  int x = (int) test[INTEGER];
  float y = (float) test[FLOAT];
  std::cout << x << std::endl;
  std::cout << y << std::endl;
  return 0;
}

要编译并运行

g++ -fPIC -std=c++11 -shared -rdynamic -o test.so test.cpp
g++ -std=c++11 -o test main.cpp -Lpath/to/the/test.so -l:test.so
LD_LIBRARY_PATH="path/to/the/test.so" ./test

2 个答案:

答案 0 :(得分:0)

使用return i时,幕后工作就是创建一个test_t类型的临时变量,该临时变量封装了int的值。由于返回类型为test::test_variant,因此在函数test_t中可以正常工作。这在函数test::operator[]中不起作用,因为返回类型为test_t&。该语言禁止创建对临时变量的可修改(左值)引用。

一种实现此目的的方法是在您的类中添加类型为test_t的数据成员,而您的测试函数operator[]会设置该成员并返回该成员,而不是返回一个临时成员。您的真实课堂很可能会做一些不同的事情。

答案 1 :(得分:0)

In C++, overload resolution does not happen on the return type, so given

int foo() { return 0; }
float foo() { return 0.f; }

there is no sanctioned way for the compiler to differentiate

int x = foo();
float f = foo();

. There is a trick using conversion operator overloads:

    #include <iostream>

    struct IntOrFloat {
            operator int () const {
                    std::cout << "returning int\n";
                    return 0;
            }

            operator float () const {
                    std::cout << "returning float\n";
                    return 0.f;
            }
    };

    IntOrFloat foo() { return IntOrFloat(); }

    int main () {
            int x = foo();
            float f = foo();
    }

You can force more verbosity by making the conversion explicit:

            explicit operator int () const ...
            explicit operator float () const ...

            int x = static_cast<int>(foo()); 
            int x = float(foo()); // old-style-cast

This proxy (or other conversion operator tricks) are as far as you'll to simulate return type overload resolution.

The idea once arised while searching a solution to supporting <euclidian vector> * <euclidian vector>-syntax, i.e. an operator* which either means dot product or vector product, depending on the type of the variable the product is assigned to.

In the end, it was not really practical and did not contribute positively to readability. The more verbose forms dot(vec, vec) and cross(vec, vec) were superior for several reasons, among which:

  • principle of least surprise: the computer graphics community is used to the terms "dot" and "cross"
  • less cryptic error messages: because this proxy technique is not idiomatic in C++, people are not used to the kind of error messages this temporal indirection yields
  • temporal and/or spatial locality: you are essentially returning a closure with code in it, which can be executed many times at many places. this can be doubly bad as it does not (actually, does) work well with auto & kind of declarations:

    int main () {
            const auto &f = foo();
            const int g = f;
            const int h = f;
            std::cout << (int)f << "\n";
    }
    

    This prints something multiple times, going hand in hand with the least surprise principle. Of course this becomes less severe if your proxy basically just forwards readily available values. But the error messages won't become any better!

Note you can also incorporate template conversion operator overloads and wild metaprogramming. While worth a fun experiment, this is not something I'd love to put into a production code base, for maintenance and readability will even decrease.

What remains? Infinite possibilities; but some of the most feasible:

  • Variant datatypes
  • Tuple datatypes (look into std::tuple, which comes with conversion operators in case of distinct member types)
  • Different idioms (e.g. named methods instead of operator method)
  • Different algorithms
  • Different data structures
  • Different design patterns