Eu continuo ouvindo muito sobre functores em C ++. Alguém pode me dar uma visão geral sobre o que são e em que casos seriam úteis?
Eu continuo ouvindo muito sobre functores em C ++. Alguém pode me dar uma visão geral sobre o que são e em que casos seriam úteis?
Um functor é basicamente apenas uma classe que define o operador (). Isso permite criar objetos com aparência de função:
// this is a functor
struct add_x {
add_x(int val) : x(val) {} // Constructor
int operator()(int y) const { return x + y; }
private:
int x;
};
// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument
std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1));
assert(out[i] == in[i] + 1); // for all i
Há algumas coisas boas sobre os functores. Uma é que, diferentemente das funções regulares, elas podem conter estado. O exemplo acima cria uma função que adiciona 42 ao que você der. Mas esse valor 42 não é codificado, foi especificado como um argumento construtor quando criamos nossa instância de functor. Eu poderia criar outro adicionador, que adicionou 27, apenas chamando o construtor com um valor diferente. Isso os torna bem personalizáveis.
Como as últimas linhas mostram, você geralmente passa functors como argumentos para outras funções, como std :: transform ou outros algoritmos de biblioteca padrão. Você poderia fazer o mesmo com um ponteiro de função regular, exceto, como eu disse acima, que os functores podem ser "personalizados" porque contêm estado, tornando-os mais flexíveis (se eu quisesse usar um ponteiro de função, teria que escrever uma função que adicionou exatamente 1. ao seu argumento.O functor é geral e adiciona tudo o que você inicializou) e também é potencialmente mais eficiente. No exemplo acima, o compilador sabe exatamente qual função std::transform
deve chamar. Deveria ligar add_x::operator()
. Isso significa que ele pode incorporar essa chamada de função. E isso a torna tão eficiente como se eu tivesse chamado manualmente a função em cada valor do vetor.
Se eu tivesse passado um ponteiro de função, o compilador não poderia ver imediatamente para qual função ele aponta, portanto, a menos que execute algumas otimizações globais bastante complexas, teria que desreferenciar o ponteiro em tempo de execução e fazer a chamada.
add42
, teria usado o functor que criei anteriormente e adicionei 42 a cada valor. Com add_x(1)
eu crio uma nova instância do functor, que adiciona apenas 1 a cada valor. É simplesmente para mostrar que, muitas vezes, você instancia o functor "on the fly", quando necessário, em vez de criá-lo primeiro e mantê-lo por perto antes de usá-lo para qualquer coisa.
operator()
, porque é isso que o chamador usa para invocá-lo. O que mais o functor tem de funções-membro, construtores, operadores e variáveis-membro é totalmente sua.
add42
seria chamado de functor, não add_x
(que é a classe do functor ou apenas a classe functor). Acho essa terminologia consistente porque os functores também são chamados de objetos de função , não classes de função. Você pode esclarecer esse ponto?
Pequena adição. Você pode usar boost::function
, para criar functors a partir de funções e métodos, desta forma:
class Foo
{
public:
void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"
e você pode usar boost :: bind para adicionar estado a este functor
boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"
e mais útil, com a função boost :: bind e boost ::, você pode criar functor a partir do método class, na verdade este é um delegado:
class SomeClass
{
std::string state_;
public:
SomeClass(const char* s) : state_(s) {}
void method( std::string param )
{
std::cout << state_ << param << std::endl;
}
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"
Você pode criar uma lista ou vetor de functores
std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
events.begin(), events.end(),
boost::bind( boost::apply<void>(), _1, e));
Há um problema com tudo isso, as mensagens de erro do compilador não são legíveis por humanos :)
operator ()
ser público no seu primeiro exemplo, já que as classes são padronizadas como privadas?
Um Functor é um objeto que age como uma função. Basicamente, uma classe que define operator()
.
class MyFunctor
{
public:
int operator()(int x) { return x * 2;}
}
MyFunctor doubler;
int x = doubler(5);
A vantagem real é que um functor pode manter o estado.
class Matcher
{
int target;
public:
Matcher(int m) : target(m) {}
bool operator()(int x) { return x == target;}
}
Matcher Is5(5);
if (Is5(n)) // same as if (n == 5)
{ ....}
int
quando deveria retornar bool
? Isso é C ++, não C. Quando essa resposta foi escrita, bool
não existia?
O nome "functor" tem sido tradicionalmente usado na teoria das categorias muito antes de o C ++ aparecer em cena. Isso não tem nada a ver com o conceito de functor em C ++. É melhor usar o nome da função de objeto em vez do que chamamos de "functor" em C ++. É assim que outras linguagens de programação chamam construções semelhantes.
Usado em vez da função comum:
Recursos:
Contras:
Usado em vez do ponteiro de função:
Recursos:
Contras:
Usado em vez da função virtual:
Recursos:
Contras:
foo(arguments)
. Portanto, ele pode conter variáveis; por exemplo, se você tivesse uma update_password(string)
função, convém acompanhar com que frequência isso aconteceu; com um functor, que pode private long time
representar o carimbo de data e hora da última vez que aconteceu. Com um ponteiro de função ou função simples, você precisa usar um fora variável de seu namespace, que só está directamente relacionada por documentação e uso, em vez de definition.l
Como outros já mencionaram, um functor é um objeto que age como uma função, ou seja, sobrecarrega o operador de chamada de função.
Functors são comumente usados em algoritmos STL. Eles são úteis porque podem manter o estado antes e entre chamadas de função, como um fechamento em linguagens funcionais. Por exemplo, você pode definir um MultiplyBy
functor que multiplique seu argumento por uma quantidade especificada:
class MultiplyBy {
private:
int factor;
public:
MultiplyBy(int x) : factor(x) {
}
int operator () (int other) const {
return factor * other;
}
};
Então você pode passar um MultiplyBy
objeto para um algoritmo como std :: transform:
int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}
Outra vantagem de um functor sobre um ponteiro para uma função é que a chamada pode ser incorporada em mais casos. Se você passou um ponteiro de função para transform
, a menos que essa chamada tenha sido incorporada e o compilador saiba que você sempre passa a mesma função para ela, ela não poderá incorporar a chamada através do ponteiro.
Para os novatos como eu, entre nós: depois de um pouco de pesquisa, descobri o que o código publicado pela metade fazia.
Um functor é um objeto de classe ou estrutura que pode ser "chamado" como uma função. Isso é possível sobrecarregando o () operator
. O () operator
(não sabe como é chamado) pode receber qualquer número de argumentos. Outros operadores usam apenas dois, ou seja, eles + operator
podem usar apenas dois valores (um de cada lado do operador) e retornar qualquer valor que você tenha sobrecarregado. Você pode ajustar qualquer número de argumentos dentro de um () operator
que é o que lhe confere flexibilidade.
Para criar um functor primeiro, você cria sua classe. Em seguida, você cria um construtor para a classe com um parâmetro de sua escolha, tipo e nome. Isso é seguido na mesma instrução por uma lista de inicializadores (que usa um operador de dois pontos, algo para o qual eu também era novo) que constrói os objetos de membro da classe com o parâmetro declarado anteriormente para o construtor. Então o () operator
está sobrecarregado. Finalmente, você declara os objetos particulares da classe ou estrutura que você criou.
Meu código (achei os nomes das variáveis de jalf confusos)
class myFunctor
{
public:
/* myFunctor is the constructor. parameterVar is the parameter passed to
the constructor. : is the initializer list operator. myObject is the
private member object of the myFunctor class. parameterVar is passed
to the () operator which takes it and adds it to myObject in the
overloaded () operator function. */
myFunctor (int parameterVar) : myObject( parameterVar ) {}
/* the "operator" word is a keyword which indicates this function is an
overloaded operator function. The () following this just tells the
compiler that () is the operator being overloaded. Following that is
the parameter for the overloaded operator. This parameter is actually
the argument "parameterVar" passed by the constructor we just wrote.
The last part of this statement is the overloaded operators body
which adds the parameter passed to the member object. */
int operator() (int myArgument) { return myObject + myArgument; }
private:
int myObject; //Our private member object.
};
Se nada disso for impreciso ou simplesmente errado, sinta-se à vontade para me corrigir!
Um functor é uma função de ordem superior que aplica uma função aos tipos parametrizados (isto é, modelo). É uma generalização da função de ordem superior do mapa . Por exemplo, poderíamos definir um functor para o std::vector
seguinte:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
std::vector<U> result;
std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
return result;
}
Essa função recebe std::vector<T>
ae retorna std::vector<U>
quando recebe uma função F
que recebe T
ae retorna a U
. Um functor não precisa ser definido sobre os tipos de contêiner, também pode ser definido para qualquer tipo de modelo, incluindo std::shared_ptr
:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
if (p == nullptr) return nullptr;
else return std::shared_ptr<U>(new U(f(*p)));
}
Heres um exemplo simples que converte o tipo em um double
:
double to_double(int x)
{
return x;
}
std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);
std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);
Existem duas leis que os functores devem seguir. A primeira é a lei de identidade, que afirma que, se o functor recebe uma função de identidade, deve ser o mesmo que aplicar a função de identidade ao tipo, ou seja, fmap(identity, x)
deve ser o mesmo que identity(x)
:
struct identity_f
{
template<class T>
T operator()(T x) const
{
return x;
}
};
identity_f identity = {};
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);
A próxima lei é a lei de composição, que afirma que, se o functor receber uma composição de duas funções, deve ser o mesmo que aplicar o functor para a primeira função e, em seguida, novamente para a segunda função. Portanto, fmap(std::bind(f, std::bind(g, _1)), x)
deve ser o mesmo que fmap(f, fmap(g, x))
:
double to_double(int x)
{
return x;
}
struct foo
{
double x;
};
foo to_foo(double x)
{
foo r;
r.x = x;
return r;
}
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));
fmap(id, x) = id(x)
e fmap(f ◦ g, x) = fmap(f, fmap(g, x))
.
Aqui está uma situação real em que fui forçado a usar um Functor para resolver meu problema:
Eu tenho um conjunto de funções (digamos, 20 delas), e todas são idênticas, exceto que cada uma chama uma função específica diferente em 3 pontos específicos.
Isso é desperdício incrível e duplicação de código. Normalmente, eu apenas passava um ponteiro de função e chamava isso nos 3 pontos. (Portanto, o código precisa aparecer apenas uma vez, em vez de vinte vezes.)
Mas então percebi, em cada caso, a função específica exigia um perfil de parâmetro completamente diferente! Às vezes 2 parâmetros, às vezes 5 parâmetros, etc.
Outra solução seria ter uma classe base, onde a função específica é um método substituído em uma classe derivada. Mas eu realmente quero construir toda essa herança, só para que eu possa passar um ponteiro de função ????
SOLUÇÃO: Então, o que fiz foi criar uma classe de wrapper (um "Functor") capaz de chamar qualquer uma das funções necessárias. Eu o configurei antecipadamente (com seus parâmetros, etc) e depois o passo em vez de um ponteiro de função. Agora, o código chamado pode acionar o Functor, sem saber o que está acontecendo por dentro. Pode até chamá-lo várias vezes (eu precisava chamá-lo três vezes).
É isso aí - um exemplo prático em que um Functor se mostrou a solução óbvia e fácil, o que me permitiu reduzir a duplicação de código de 20 funções para 1.
Exceto pelos usados no retorno de chamada, os functores C ++ também podem ajudar a fornecer um estilo de acesso semelhante ao Matlab a uma classe de matriz . Há um exemplo .
operator()
mas não o uso das propriedades do objeto de função.
Como foi repetido, functores são classes que podem ser tratadas como funções (operador de sobrecarga ()).
Eles são mais úteis para situações nas quais você precisa associar alguns dados a chamadas repetidas ou atrasadas para uma função.
Por exemplo, uma lista vinculada de functores pode ser usada para implementar um sistema síncrono básico de corotina, um despachante de tarefas ou análise de arquivos interruptíveis. Exemplos:
/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
std::string output;
Functor(const std::string& out): output(out){}
operator()() const
{
std::cout << output << " ";
}
};
int main(int argc, char **argv)
{
std::list<Functor> taskQueue;
taskQueue.push_back(Functor("this"));
taskQueue.push_back(Functor("is a"));
taskQueue.push_back(Functor("very simple"));
taskQueue.push_back(Functor("and poorly used"));
taskQueue.push_back(Functor("task queue"));
for(std::list<Functor>::iterator it = taskQueue.begin();
it != taskQueue.end(); ++it)
{
*it();
}
return 0;
}
/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
std::cout << "i = " << i << std::endl;
std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
std::cin >> should_increment;
return 2;
}
void doSensitiveWork()
{
++i;
should_increment = false;
}
class BaseCoroutine
{
public:
BaseCoroutine(int stat): status(stat), waiting(false){}
void operator()(){ status = perform(); }
int getStatus() const { return status; }
protected:
int status;
bool waiting;
virtual int perform() = 0;
bool await_status(BaseCoroutine& other, int stat, int change)
{
if(!waiting)
{
waiting = true;
}
if(other.getStatus() == stat)
{
status = change;
waiting = false;
}
return !waiting;
}
}
class MyCoroutine1: public BaseCoroutine
{
public:
MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
BaseCoroutine& partner;
virtual int perform()
{
if(getStatus() == 1)
return doSomeWork();
if(getStatus() == 2)
{
if(await_status(partner, 1))
return 1;
else if(i == 100)
return 0;
else
return 2;
}
}
};
class MyCoroutine2: public BaseCoroutine
{
public:
MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
bool& work_signal;
virtual int perform()
{
if(i == 100)
return 0;
if(work_signal)
{
doSensitiveWork();
return 2;
}
return 1;
}
};
int main()
{
std::list<BaseCoroutine* > coroutineList;
MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
MyCoroutine1 *printer = new MyCoroutine1(incrementer);
while(coroutineList.size())
{
for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
it != coroutineList.end(); ++it)
{
*it();
if(*it.getStatus() == 0)
{
coroutineList.erase(it);
}
}
}
delete printer;
delete incrementer;
return 0;
}
Obviamente, esses exemplos não são úteis em si mesmos. Eles apenas mostram como os functores podem ser úteis, eles próprios são muito básicos e inflexíveis e isso os torna menos úteis do que, por exemplo, o que o impulso proporciona.
Functors são usados no gtkmm para conectar algum botão da GUI a uma função ou método C ++ real.
Se você usar a biblioteca pthread para tornar seu aplicativo multithread, os Functors podem ajudá-lo.
Para iniciar um encadeamento, um dos argumentos de pthread_create(..)
é o ponteiro da função a ser executado em seu próprio encadeamento.
Mas há um inconveniente. Esse ponteiro não pode ser um ponteiro para um método, a menos que seja um método estático ou a menos que você especifique sua classe , como class::method
. E outra coisa, a interface do seu método pode ser apenas:
void* method(void* something)
Portanto, você não pode executar (de uma maneira simples e óbvia) métodos da sua classe em um thread sem fazer algo extra.
Uma maneira muito boa de lidar com threads em C ++ é criar sua própria Thread
classe. Se você queria executar métodos da MyClass
classe, o que eu fiz foi transformar esses métodos em Functor
classes derivadas.
Além disso, a Thread
classe possui este método:
static void* startThread(void* arg)
Um ponteiro para esse método será usado como argumento a ser chamado pthread_create(..)
. E o que startThread(..)
deve receber em arg é uma void*
referência convertida a uma instância no heap de qualquer Functor
classe derivada, que será convertida novamente Functor*
quando executada e depois denominada run()
método.
Para adicionar, usei objetos de função para ajustar um método legado existente ao padrão de comando; (único lugar em que a beleza do paradigma OO era verdadeira OCP); Também adicionando aqui o padrão do adaptador de função relacionado.
Suponha que seu método tenha a assinatura:
int CTask::ThreeParameterTask(int par1, int par2, int par3)
Veremos como podemos ajustá-lo para o padrão Command - para isso, primeiro, você precisa escrever um adaptador de função membro para que ele possa ser chamado como objeto de função.
Nota - isso é feio, e pode ser que você possa usar os auxiliares de ligação do Boost, etc., mas se você não puder ou não quiser, esse é um caminho.
// a template class for converting a member function of the type int function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
:m_Ptr(_Pm) //okay here we store the member function pointer for later use
{}
//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};
Além disso, precisamos de um método auxiliar mem_fun3 para a classe acima para ajudar na chamada.
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm) (_arg1,_arg2,_arg3) )
{
return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));
}
Agora, para vincular os parâmetros, precisamos escrever uma função do fichário. Então, aqui vai:
template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
:m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}
//and this is the function object
void operator()() const
{
m_fn(m_ptr,m1,m2,m3);//that calls the operator
}
private:
_Ptr m_ptr;
_Func m_fn;
_arg1 m1; _arg2 m2; _arg3 m3;
};
E, uma função auxiliar para usar a classe binder3 - bind3:
//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}
Agora, temos que usar isso com a classe Command; use o seguinte typedef:
typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
method3 = p_method;
}
Aqui está como você chama:
F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3(
&CTask::ThreeParameterTask), task1,2122,23 );
Nota: f3 (); chamará o método task1-> ThreeParameterTask (21,22,23) ;.
O contexto completo desse padrão no link a seguir
Uma grande vantagem da implementação de funções como functors é que eles podem manter e reutilizar o estado entre as chamadas. Por exemplo, muitos algoritmos de programação dinâmica, como o algoritmo Wagner-Fischer para calcular a distância de Levenshtein entre cadeias, funcionam preenchendo uma grande tabela de resultados. É muito ineficiente alocar essa tabela toda vez que a função é chamada, portanto, implementar a função como um functor e tornar a tabela uma variável membro pode melhorar muito o desempenho.
Abaixo está um exemplo de implementação do algoritmo Wagner-Fischer como um functor. Observe como a tabela é alocada no construtor e depois reutilizada operator()
, com o redimensionamento conforme necessário.
#include <string>
#include <vector>
#include <algorithm>
template <typename T>
T min3(const T& a, const T& b, const T& c)
{
return std::min(std::min(a, b), c);
}
class levenshtein_distance
{
mutable std::vector<std::vector<unsigned int> > matrix_;
public:
explicit levenshtein_distance(size_t initial_size = 8)
: matrix_(initial_size, std::vector<unsigned int>(initial_size))
{
}
unsigned int operator()(const std::string& s, const std::string& t) const
{
const size_t m = s.size();
const size_t n = t.size();
// The distance between a string and the empty string is the string's length
if (m == 0) {
return n;
}
if (n == 0) {
return m;
}
// Size the matrix as necessary
if (matrix_.size() < m + 1) {
matrix_.resize(m + 1, matrix_[0]);
}
if (matrix_[0].size() < n + 1) {
for (auto& mat : matrix_) {
mat.resize(n + 1);
}
}
// The top row and left column are prefixes that can be reached by
// insertions and deletions alone
unsigned int i, j;
for (i = 1; i <= m; ++i) {
matrix_[i][0] = i;
}
for (j = 1; j <= n; ++j) {
matrix_[0][j] = j;
}
// Fill in the rest of the matrix
for (j = 1; j <= n; ++j) {
for (i = 1; i <= m; ++i) {
unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
matrix_[i][j] =
min3(matrix_[i - 1][j] + 1, // Deletion
matrix_[i][j - 1] + 1, // Insertion
matrix_[i - 1][j - 1] + substitution_cost); // Substitution
}
}
return matrix_[m][n];
}
};
Eu "descobri" um uso muito interessante de functors: eu os uso quando não tenho um bom nome para um método, pois um functor é um método sem nome ;-)
operator()(...)
significa: está sobrecarregando o operador "chamada de função" . É simplesmente uma sobrecarga do()
operador para o operador. Não se enganeoperator()
em chamar uma função chamadaoperator
, mas veja-a como o operador usual sobrecarregando a sintaxe.