web-dev-qa-db-fra.com

Pouvez-vous faire des opérateurs personnalisés en C ++?

Est-il possible de faire un opérateur personnalisé afin que vous puissiez faire des choses comme ça?

if ("Hello, world!" contains "Hello") ...

NOTE: Ceci est une question distincte de "Est-ce une bonne idée de ...";)

40
Cogwheel

Oui! (Eh bien, en quelque sorte)

Il y a quelques outils accessibles au public pour vous aider. Utilisez la génération de code de préprocesseur pour créer des modèles qui implémentent les opérateurs personnalisés. Ces opérateurs se composent d'un ou de plusieurs opérateurs intégrés conjointement avec un identifiant.

Comme ils ne sont pas réellement des opérateurs personnalisés, mais simplement des tours de surcharge de l'opérateur, il y a quelques mises en garde:

  • Les macros sont diaboliques. Si vous faites une erreur, le compilateur sera tout à fait inutile pour suivre le problème.
  • Même si vous obtenez la macro droite, s'il y a une erreur dans votre utilisation de l'opérateur ou dans la définition de votre opération, le compilateur ne sera que légèrement plus utile.
  • Vous devez utiliser un identifiant valide dans le cadre de l'opérateur. Si vous voulez un opérateur de type plus symbole, vous pouvez utiliser _, o ou alphanumériques simples similaires.

CustomOperators

Bien que je travaillais sur ma propre bibliothèque à cette fin (voir ci-dessous), j'ai rencontré ce projet. Voici un exemple de création d'un opérateur avg:

#define avg BinaryOperatorDefinition(_op_avg, /)
DeclareBinaryOperator(_op_avg)
DeclareOperatorLeftType(_op_avg, /, double);
inline double _op_avg(double l, double r)
{
   return (l + r) / 2;
}
BindBinaryOperator(double, _op_avg, /, double, double)

idop

Ce qui a commencé comme n exercice de frivolité pure est devenu mon propre problème. Voici un exemple similaire:

template<typename T> class AvgOp { 
public: 
   T operator()(const T& left, const T& right) 
   {
      return (left + right) / 2; 
   }
};
IDOP_CREATE_LEFT_HANDED(<, _avg_, >, AvgOp)
#define avg <_avg_>

Différences clés

  • Customoperators prend en charge les opérateurs postfix unaires
  • Les modèles IDOP utilisent des références plutôt que des pointeurs pour éliminer l'utilisation du magasin libre et permettre une évaluation complète de la compilation de l'opération
  • Idop vous permet de spécifier facilement plusieurs opérations pour le même identifiant racine
35
Cogwheel

Il existe une méthode parfaitement explorée dans "aspartame syntaxtique" par Sander Stoks qui vous permettrait d'utiliser le format suivant:

if ("Hello, world!" <contains> "Hello") ...

En substance, vous avez besoin d'un objet proxy avec les opérateurs '<' et '>' surchargé. Le proxy fait tout le travail; "Contient 'peut simplement être un singleton sans comportement ni données.

// Not my code!
const struct contains_ {} contains;

template <typename T>
struct ContainsProxy
{
    ContainsProxy(const T& t): t_(t) {}
    const T& t_;
};

template <typename T>
ContainsProxy<T> operator<(const T& lhs, const contains_& rhs)
{
    return ContainsProxy<T>(lhs);
}

bool operator>(const ContainsProxy<Rect>& lhs, const Rect& rhs)
{
    return lhs.t_.left   <= rhs.left && 
           lhs.t_.top    <= rhs.top && 
       lhs.t_.right  >= rhs.right && 
       lhs.t_.bottom >= rhs.bottom;
}
12
John P

Pour être un peu plus précis, c ++ lui-même ne prend en charge que la création de nouvelles surcharges d'opérations existantes, ne pas créer de nouveaux opérateurs. Il existe des langues (par exemple, ML et la plupart de ses descendants) qui vous permettent de créer des opérateurs entièrement nouveaux, mais C++ n'est pas l'un d'entre eux.

De l'apparence des choses, (au moins) la bibliothèque CustomOpérators mentionnée dans l'autre réponse ne prend pas non plus en charge les opérateurs personnalisés non plus. Au moins si je lis les choses correctement, c'est (en interne) traduire votre opérateur personnalisé en une surcharge d'un opérateur existant. Cela rend les choses plus faciles, au détriment d'une certaine flexibilité - par exemple, lorsque vous créez un nouvel opérateur en ML, vous pouvez lui donner la préséance différente de celle d'un opérateur intégré.

2
Jerry Coffin

J'ai créé les deux macros suivantes:

#define define const struct
#define operator(ReturnType, OperatorName, FirstOperandType, SecondOperandType) OperatorName ## _ {} OperatorName; template <typename T> struct OperatorName ## Proxy{public:OperatorName ## Proxy(const T& t) : t_(t){}const T& t_;static ReturnType _ ## OperatorName ## _(const FirstOperandType a, const SecondOperandType b);};template <typename T> OperatorName ## Proxy<T> operator<(const T& lhs, const OperatorName ## _& rhs){return OperatorName ## Proxy<T>(lhs);}ReturnType operator>(const OperatorName ## Proxy<FirstOperandType>& lhs, const SecondOperandType& rhs){return OperatorName ## Proxy<FirstOperandType>::_ ## OperatorName ## _(lhs.t_, rhs);}template <typename T> inline ReturnType OperatorName ## Proxy<T>::_ ## OperatorName ## _(const FirstOperandType a, const SecondOperandType b)

Ensuite, vous auriez juste de définir votre opérateur personnalisé comme dans l'exemple suivant:

define operator(bool, myOr, bool, bool) { // Arguments are the return type, the name of the operator, the left operand type and the right operand type, respectively
    return a || b;
}

#define myOr <myOr> // Finally, you have to define a macro to avoid to put the < and > operator at the start and end of the operator name

Une fois la fois que vous avez défini votre opérateur, vous pouvez l'utiliser comme opérateur prédéfini:

bool a = true myOr false;
// a == true
1
Davide Cannizzo

Techniquement, non. C'est-à-dire que vous ne pouvez pas prolonger l'ensemble de operator+, operator-, etc. Mais ce que vous proposez dans votre exemple est autre chose. Vous vous demandez s'il y a une définition de "contient" de telle sorte que string-literal "contains" string-literal est une expression, avec une logique non triviale (#define contains "" étant le cas trivial).

Il n'y a pas beaucoup d'expressions qui peuvent avoir la forme string-literal X string-literal. C'est parce que les littéraux à cordes elles-mêmes sont des expressions. Donc, vous recherchez une règle de langue de la forme expr X expr. Il y en a plusieurs quelques-uns, mais ils sont tous des règles pour les opérateurs et ceux qui ne fonctionnent pas sur des cordes. Malgré la mise en œuvre évidente, "Hello, " + "world" n'est pas une expression valide. Alors, qu'est-ce que sinon peut être dans string-literal X string-literal? Cela ne peut pas être une expression elle-même. Il ne peut pas être un nom de typage, un nom Typedef ou un nom de modèle. Il ne peut pas être un nom de la fonction. Cela ne peut vraiment être qu'une macro, qui sont les seules entités nommées restantes. Pour cela, voir "Oui (bien, sorte de)" réponse.

0
MSalters