risorse | operatori infissi in c++

Operatori infissi in C++

Introduzione

In questo periodo al quotidiano lavoro in C++ si è affiancato qualche piccolo progetto Python. Ho assimilato la sintassi di questo linguaggio a tal punto che spesso e volentieri mi capita di “pythonizzare” il mio C++: oltre a dimenticarmi sistematicamente le parentesi graffe, mi capita per esempio di scrivere:

  int i = 3;
  std::vector<int> numbers { 1, 2, 3, 4, 5, };
  ...

  if (i in numbers)
    ...

Mi sono così chiesto se esiste il modo di rendere compilabile codice come questo.

Compilabilità

Il codice di partenza è il seguente:

#include <iostream>
#include <vector>

int main() {
  std::vector<int> numbers { 1, 2, 3, 4, 5, };

  int i = 3;
  if (i in numbers) // compiler error!
    std::cout << i << " found\n";
  else
    std::cout << i << " not found\n";
}

Una forma sintattica non troppo difforme da quella desiderata che con poca fatica si può rendere compilabile è la seguente:

  if (i +in+ numbers)

Supponendo che in sia un oggetto di un tipo opportuno, e tenendo conto dell'associatività dell'operatore +, il valore dell'espressione viene determinato in tre passi:

  1. valutazione della sotto-espressione i + in;
  2. valutazione della sotto-espressione x + numbers, dove x rappresenta il risultato di i + in;
  3. cast implicito a bool del risultato.

Affinché tale espressione sia compilabile, è sufficiente che l'oggetto in supporti:

Ecco come si presenta il codice dopo le modifiche suggerite:

#include <iostream>
#include <vector>

struct in_operator {
  operator bool() const { return false; }
  template <class T> in_operator& operator+(const T&) { return *this; }
} in;

template <class T>
in_operator& operator+(const T&, in_operator& op) {
  return op;
}

int main() {
  std::vector<int> numbers { 1, 2, 3, 4, 5, };

  int i = 3;
  if (i +in+ numbers)
    std::cout << i << " found\n";
  else
    std::cout << i << " not found\n";
}

/* output:
 *
 * 3 not found
 */

È evidente che il programma non funziona, però compila!

Eliminazione del cast implicito

Si può evitare la conversione implicita a bool se si ha l'accortezza di ritornare il booleano atteso direttamente dall'operator+ membro:

#include <iostream>
#include <vector>

struct in_operator {
  operator bool() const { return false; }
  template <class T> in_operator& bool operator+(const T&) { return *this false; }
} in;

template <class T>
in_operator& operator+(const T&, in_operator& op) {
  return op;
}

int main() {
  std::vector<int> numbers { 1, 2, 3, 4, 5, };

  int i = 3;
  if (i +in+ numbers)
    std::cout << i << " found\n";
  else
    std::cout << i << " not found\n";
}

/* output:
 *
 * 3 not found
 */

Implementazione

Cattura dell'operando di sinistra

Poiché è necessario conoscere il valore del primo termine nel contesto del secondo operator+ (onde ritornare il valore di verità corretto), è indispensabile “catturarlo”, e il modo più semplice per farlo è inglobarlo in un oggetto ausiliario ad hoc nella prima chiamata a operator+, dov'è disponibile una sua referenza:

#include <iostream>
#include <vector>

#include <algorithm>

struct in_operator {
  template <class T> bool operator+(const T&) { return false; }
} in;

template <class T>
struct in_operator_impl {
  const T& lhs_;
  in_operator_impl(const T& lhs) : lhs_(lhs) {}
  template <class Sequence>
  bool operator+(const Sequence& s) {
    return std::find(std::begin(s), std::end(s), lhs_) != std::end(s);
  }
};

template <class T>
in_operator& operator+(const T&, in_operator& op) {
  return op;
}

template <class T>
in_operator_impl<T> operator+(const T& lhs, in_operator&) {
  return lhs;
}

int main() {
  std::vector<int> numbers { 1, 2, 3, 4, 5, };

  int i = 3;
  if (i +in+ numbers)
    std::cout << i << " found\n";
  else
    std::cout << i << " not found\n";
}

/* output:
 *
 * 3 found
 */

Il programma ora funziona: l'espressione i + in dà origine ad un oggetto temporaneo di tipo in_operator_impl<int> il quale, attraverso l'applicazione dell'operator+ membro invocato durante la valutazione della sotto-espressione x + numbers, fornisce il risultato booleano atteso.

Zucchero sintattico

Ci sono almeno due aspetti discutibili nell'implementazione: la presenza dell'oggetto statico/globale in e la sintassi dell'operatore, che non è ancora nella forma desiderata. Entrambi i problemi sono però facilmente risolvibili introducendo una macro che si occupa di inserire i due caratteri + e che sostituisce l'oggetto statico con uno temporaneo:

#include <iostream>
#include <vector>

#include <algorithm>

struct in_operator {} in;

template <class T>
struct in_operator_impl {
  const T& lhs_;
  in_operator_impl(const T& lhs) : lhs_(lhs) {}
  template <class Sequence>
  bool operator+(const Sequence& s) {
    return std::find(std::begin(s), std::end(s), lhs_) != std::end(s);
  }
};

template <class T>
in_operator_impl<T> operator+(const T& lhs, in_operator&) {
in_operator_impl<T> operator+(const T& lhs, in_operator&&) {
  return lhs;
}

#define in +in_operator()+

int main() {
  std::vector<int> numbers { 1, 2, 3, 4, 5, };

  int i = 3;
  if (i +in+ numbers)
  if (i in numbers)
    std::cout << i << " found\n";
  else
    std::cout << i << " not found\n";

  int j = 7;
  if (j in numbers)
    std::cout << j << " found\n";
  else
    std::cout << j << " not found\n";
}

/* output:
 *
 * 3 found
 * 7 not found
 */

Pagina modificata il 26/06/2015