оператор overload [] для возврата типа варианта - PullRequest
0 голосов
/ 07 января 2019

РЕДАКТИРОВАТЬ: благодаря ответам я смог решить все проблемы с моим кодом. Я выкладываю здесь решение: оно может пригодиться кому-то в будущем. В частности, предложение об использовании прокси-класса оказалось очень полезным! Пример не рассматривает все случаи, но добавить вариант к варианту будет тривиально!

Я пишу пользовательский класс C ++ (C11 - Linux), который ведет себя как неупорядоченная карта {ключ, значение} . Я хотел бы перегрузить оператор [] , чтобы я мог использовать класс с тем же синтаксисом, что и у неупорядоченной карты: объект [ключ] вернул бы значение,

Проблема в том, что мне нужен объект [ключ] для возврата типа варианта. Я могу хранить внутренне значение в виде строки или структуры, но когда я получаю его с помощью object [key] , , мне нужно, чтобы возвращаемое значение было int , float или string в зависимости от некоторых внутренних условий, определенных во время выполнения .

Вот почему я думал об использовании boost :: option library ... но я открыт для любых других предложений. Единственное ограничение заключается в том, что класс test (в примере) должен быть скомпилирован как разделяемая библиотека .so, и что код должен быть C11-совместимым (я имею в виду, компилируемым GNU g ++ 4.8.5).

Я написал простой пример, чтобы показать, какое поведение я хотел бы Этот пример ничего не значит. Это просто, чтобы проиллюстрировать вид ошибки, которую я получаю. Реальный класс, который я пишу, имеет другую структуру, но использование перегрузки bool :: variable и 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 голосов
/ 07 января 2019

В C ++ разрешение перегрузки не возвращается для возвращаемого типа, поэтому с учетом

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

нет санкционированного способа различения компилятором

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

. Есть хитрость с использованием перегрузок операторов преобразования:

    #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();
    }

Вы можете усилить многословие, выполнив преобразование explicit:

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

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

Этот прокси-сервер (или другие приемы оператора преобразования) позволяют имитировать разрешение перегрузки возвращаемого типа.

Идея однажды возникла при поиске решения для поддержки <euclidian vector> * <euclidian vector> -синтаксиса, то есть operator*, что означает точечное произведение или векторное произведение , в зависимости от типа переменная, которой присвоен продукт.

В конце концов, это было не очень практично и не способствовало удобочитаемости. Более подробные формы dot(vec, vec) и cross(vec, vec) были лучше по нескольким причинам, среди которых:

  • принцип наименьшего удивления: в сообществе компьютерной графики используются термины «точка» и «крест»
  • меньше загадочных сообщений об ошибках: поскольку этот прокси-метод не является идиоматическим в C ++, люди не привыкли к типу сообщений об ошибках, которые дает эта временная косвенность
  • временная и / или пространственная локальность: вы по существу возвращаете замыкание с кодом в нем, которое может быть выполнено много раз во многих местах. это может быть вдвойне плохо, поскольку (на самом деле не работает ) хорошо работает с auto & объявлениями:

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

    Это печатает что-то несколько раз, рука об руку с принципом наименьшего удивления. Конечно, это становится менее серьезным, если ваш прокси-сервер просто перенаправляет легкодоступные значения. Но сообщения об ошибках не станут лучше!

Обратите внимание, что вы также можете включить перегрузки операторов преобразования шаблонов и дикое метапрограммирование. Хотя это и стоит забавный эксперимент, это не то, что я хотел бы поместить в базу производственного кода, поскольку обслуживание и читаемость даже уменьшатся.

Что остается? Бесконечные возможности; но некоторые из наиболее выполнимых:

  • Варианты типов данных
  • Типы кортежей (см. std::tuple, который поставляется с операторами преобразования в случае отдельных типов элементов)
  • Различные идиомы (например, именованные методы вместо операторного метода)
  • Различные алгоритмы
  • Различные структуры данных
  • Различные шаблоны дизайна
0 голосов
/ 07 января 2019

Когда вы используете return i, под капотом происходит создание временного типа test_t, который инкапсулирует это значение int. Это прекрасно работает в функции test::test_variant, потому что тип возвращаемого значения test_t. Это не может работать в функции test::operator[], потому что тип возвращаемого значения test_t&. Язык запрещает создание модифицируемой (l-value) ссылки на временный.

Один из способов сделать эту работу - добавить в класс элемент данных типа test_t, при этом ваша тестовая функция operator[] устанавливает этот элемент и возвращает его, а не возвращает временный. Ваш реальный класс, скорее всего, сделает что-то другое.

...