6 votes

Façon élégante d'exprimer shift left OR right dans un template.

J'ai actuellement une fonction de modèle qui, en fonction de ses paramètres de modèle A et B, peut décaler une valeur vers la gauche ou vers la droite :

template <int A, int B> void f(X) {
// ...
if (A >= B)
{
  SetValue(X << (A-B));
}
else // (A < B)
{
  SetValue(X >> (B-A));
}

Lorsque j'instancie le modèle pour A<B je reçois un avertissement pour un décalage négatif à droite sur la première branche (inaccessible), et sinon je reçois un avertissement pour un décalage négatif à gauche sur la première branche. Notre base de code est exempte d'avertissements, ce qui n'est pas acceptable. Existe-t-il une alternative concise et lisible à ces deux déclarations de décalage ?

Des questions similaires (par exemple Déplacement dynamique vers la gauche OU la droite ) n'ont pas cet avertissement intempestif car la distance de décalage est une variable d'exécution.

6voto

ForEveR Points 28133

Avec C++11 ou boost.

template<int A, int B>
void f_impl(typename std::enable_if<(A >= B)>::type* = 0)
{
   // first case
}

template<int A, int B>
void f_impl(typename std::enable_if<(A < B)>::type* = 0)
{
   // second case
}

template<int A, int B>
void f()
{
   f_impl<A, B>();
}

4voto

davmac Points 4317

Transformez le résultat de (A-B) et (B-A) en unsigned, et masquez-le en plus (bitwise-and) avec (sizeof(int) - 1) . Ceci efface l'avertissement pour GCC 5.5 et 6.3. Pour les versions plus récentes de GCC, aucun avertissement n'est généré.

template <int A, int B> void f(int X) {
  // ...
  if (A >= B)
  {
    SetValue(X << ((unsigned)(A-B) & (sizeof(int) - 1)));
  }
  else // (A < B)
  {
    SetValue(X >> ((unsigned)(B-A) & (sizeof(int) - 1)));
  }
}

Nota pour répondre aux divers commentaires sur le comportement indéfini : le seul cas où la solution proposée pourrait provoquer un comportement indéfini est celui où l'on effectue un décalage d'une quantité supérieure à la largeur de bit de l'opérande. Cependant, ceci est protégé par la comparaison ; en supposant que la différence entre A et B est un nombre de décalage sûr, ce qui est implicite dans la question, alors if (A >= B) garantit que seul un déplacement avec ce montant est effectivement exécuté. L'autre branche de la if La déclaration est no exécuté et n'effectue donc pas de décalage et ne peut pas produire un comportement indéfini à partir du décalage (bien que si elle était exécutée, elle le ferait certainement).

Quelques commentateurs ont affirmé que la branche qui n'est pas exécutée peut encore provoquer un comportement indéfini. J'ai du mal à comprendre comment une telle erreur de compréhension peut se produire. Considérons le code suivant :

int *a = nullptr;

if (a != nullptr) {
    *a = 4;
}

Or, si le déréférencement d'un pointeur nul provoque un comportement indéfini même lorsqu'il n'est pas exécuté, la condition de garde devient inutile. Ce n'est clairement pas le cas. Le code ci-dessus est parfaitement bien ; il assigne a une valeur de nullptr et ne déréférence pas ensuite a en raison de la garde. Bien que des exemples aussi évidents (avec l'affectation à null immédiatement suivie d'une vérification de null) n'aient pas tendance à se produire dans le code réel, la "déréférence gardée" en général est un idiome commun. Il ne produit certainement pas en lui-même un comportement indéfini si le pointeur vérifié est effectivement nul ; c'est pourquoi la garde est utile.

3voto

James Kanze Points 96599

Le plus évident est de transmettre à une fonction prenant un argument supplémentaire :

template <bool Cond> struct Discrim {};

template <int A, int B>
void f( Discrim<false> )
{
    SetValue( X, (A - B) );
}

template <int A, int B>
void f( Discrim<true> )
{
    SetValue( X, (B - A) );
}

template <int A, int B>
void f()
{
    f( Discrim< (A < B) >() );
}

(L'utilisation d'un tel modèle de classe Discrim est l'une des plus simples techniques de métaprogrammation).

1voto

TemplateRex Points 26447

C'est ce que j'utilise dans mon moteur de jeu de dames qui fait largement appel aux bitboards pour la représentation des planches.

namespace detail {

enum { Left, Right };

template<typename, std::size_t>
struct Shift;

// partial specialization for bitwise shift-left
template<std::size_t N>
struct Shift<Left, N>
{
        template<typename T>
        T operator()(T x) const
        {
                return x << N;
        }
};

// partial specialization for bitwise shift-right
template<std::size_t N>
struct Shift<Right, N>
{
        template<typename T>
        T operator()(T x) const
        {
                return x >> N;
        }
};

} // namespace detail

template<int N>
struct Shift
{
        template<typename T>
        T operator()(T x)
        {            
            return N >= 0 ? detail::Shift<Left, N>()(x) : detail::Shift<Right, -N>()(x);
        }
};

template <int A, int B> 
void f(int x)
{
     SetValue(Shift<A-B>()(x));
}

Vous pouvez faire quelque chose de similaire pour ShiftAssign ( <<= y >>= ).

1voto

MSalters Points 74024

Le commentaire de davmac ("utiliser &0x1F") était la bonne idée, sauf pour la largeur maximale supposée du décalage. Cela a été facilement corrigé :

template <int A, int B> void f(X) {
// ...
if (A >= B)
{
  SetValue(X << abs(A-B));
}
else // (A < B)
{
  SetValue(X >> abs(B-A));
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X