web-dev-qa-db-fra.com

Comment éviter les conversions implicites sur des fonctions non constructrices?

Comment éviter la conversion implicite de fonctions non constructives?
J'ai une fonction qui prend un entier comme paramètre,
mais cette fonction prendra également des caractères, des bools et des longs.
Je crois qu'il fait cela en les moulant implicitement.
Comment puis-je éviter cela afin que la fonction accepte uniquement les paramètres d'un type correspondant et refuse de compiler autrement?
Il existe un mot clé "explicite" mais il ne fonctionne pas sur les fonctions non constructrices. : \
que fais-je?

Le programme suivant se compile, bien que je ne souhaite pas:

#include <cstdlib>

//the function signature requires an int
void function(int i);

int main(){

    int i{5};
    function(i); //<- this is acceptable

    char c{'a'};
    function(c); //<- I would NOT like this to compile

    return EXIT_SUCCESS;
}

void function(int i){return;}

* veuillez vous assurer de signaler toute utilisation abusive de la terminologie et des hypothèses

38
Trevor Hickey

Vous ne pouvez pas directement, car un char est automatiquement promu en int.

Vous pouvez cependant recourir à une astuce: créez une fonction qui prend un char comme paramètre et ne l'implémentez pas. Il se compilera, mais vous obtiendrez une erreur de l'éditeur de liens:

void function(int i) 
{
}
void function(char i);
//or, in C++11
void function(char i) = delete;

L'appel de la fonction avec un paramètre char interrompra la génération.

Voir http://ideone.com/2SRdM

Terminologie: fonctions non constructrices? Voulez-vous dire une fonction qui n'est pas un constructeur?

21
Luchian Grigore

Définissez un modèle de fonction qui correspond à tous les autres types:

void function(int); // this will be selected for int only

template <class T>
void function(T) = delete; // C++11 

Cela est dû au fait que les fonctions non modèles avec correspondance directe sont toujours considérées en premier. Ensuite, le modèle de fonction avec correspondance directe est pris en compte - donc jamais function<int> sera utilisé. Mais pour toute autre chose, comme char, function<char> sera utilisé - et cela donne des erreurs de compilation:

void function(int) {}

template <class T>
void function(T) = delete; // C++11 


int main() {
   function(1);
   function(char(1)); // line 12
} 

LES ERREURS:

prog.cpp: In function 'int main()':
prog.cpp:4:6: error: deleted function 'void function(T) [with T = char]'
prog.cpp:12:20: error: used here

C'est la manière C++ 03:

// because this ugly code will give you compilation error for all other types
class DeleteOverload
{
private:
    DeleteOverload(void*);
};


template <class T>
void function(T a, DeleteOverload = 0);

void function(int a)
{}
53
PiotrNycz

Voici une solution générale qui provoque une erreur au moment de la compilation si function est appelé avec autre chose qu'un int

template <typename T>
struct is_int { static const bool value = false; };

template <>
struct is_int<int> { static const bool value = true; };


template <typename T>
void function(T i) {
  static_assert(is_int<T>::value, "argument is not int");
  return;
}

int main() {
  int i = 5;
  char c = 'a';

  function(i);
  //function(c);

  return 0;
}

Cela fonctionne en permettant à n'importe quel type d'argument de fonctionner mais en utilisant is_int comme prédicat au niveau du type. L'implémentation générique de is_int a une valeur fausse mais la spécialisation explicite pour le type int a la valeur true de sorte que l'assertion statique garantit que l'argument a exactement le type int sinon il y a une erreur de compilation.

7
Geoff Reedy

Eh bien, j'allais répondre à cela avec le code ci-dessous, mais même s'il fonctionne avec Visual C++, dans le sens de produire l'erreur de compilation souhaitée, MinGW g ++ 4.7.1 l'accepte et invoque le constructeur de référence rvalue!

Je pense que ce doit être un bug du compilateur, mais je peux me tromper, alors - n'importe qui?

Quoi qu'il en soit, voici le code, qui peut se révéler être une solution conforme aux normes (ou, il se peut que ce soit un thinko de ma part!):

#include <iostream>
#include <utility>      // std::is_same, std::enable_if
using namespace std;

template< class Type >
struct Boxed
{
    Type value;

    template< class Arg >
    Boxed(
        Arg const& v,
        typename enable_if< is_same< Type, Arg >::value, Arg >::type* = 0
        )
        : value( v )
    {
        wcout << "Generic!" << endl;
    }

    Boxed( Type&& v ): value( move( v ) )
    {
        wcout << "Rvalue!" << endl;
    }
};

void function( Boxed< int > v ) {}

int main()
{
    int i = 5;
    function( i );  //<- this is acceptable

    char c = 'a';
    function( c );  //<- I would NOT like this to compile
}
1

Vous pouvez peut-être utiliser une structure pour rendre la deuxième fonction privée:

#include <cstdlib>

struct NoCast {
    static void function(int i);
  private:
    static void function(char c);
};

int main(){

    int i(5);
    NoCast::function(i); //<- this is acceptable

    char c('a');
    NoCast::function(c); //<- Error

    return EXIT_SUCCESS;
}

void NoCast::function(int i){return;}

Cela ne compilera pas:

prog.cpp: In function ‘int main()’:
prog.cpp:7: error: ‘static void NoCast::function(char)’ is private
prog.cpp:16: error: within this context
1
alestanis

Pour C++ 14 (et je crois C++ 11), vous pouvez également désactiver les constructeurs de copie en surchargeant rvalue-references:

Exemple: supposons que vous ayez une classe de base Binding<C>, Où C est soit la classe de base Constraint, soit une classe héritée. Supposons que vous stockez Binding<C> Par valeur dans un vecteur, que vous transmettez une référence à la liaison et que vous souhaitez vous assurer de ne pas provoquer de copie implicite.

Vous pouvez le faire en supprimant func(Binding<C>&& x) (selon l'exemple de PiotrNycz) pour les cas spécifiques de référence de valeur.

Fragment:

template<typename T>
void overload_info(const T& x) {
  cout << "overload: " << "const " << name_trait<T>::name() << "&" << endl;
}

template<typename T>
void overload_info(T&& x) {
  cout << "overload: " << name_trait<T>::name() << "&&" << endl;
}

template<typename T>
void disable_implicit_copy(T&& x) = delete;

template<typename T>
void disable_implicit_copy(const T& x) {
  cout << "[valid] ";
  overload_info<T>(x);
}

...

int main() {
  Constraint c;
  LinearConstraint lc(1);

  Binding<Constraint> bc(&c, {});
  Binding<LinearConstraint> blc(&lc, {});

  CALL(overload_info<Binding<Constraint>>(bc));
  CALL(overload_info<Binding<LinearConstraint>>(blc));

  CALL(overload_info<Binding<Constraint>>(blc));

  CALL(disable_implicit_copy<Binding<Constraint>>(bc));
  // // Causes desired error
  // CALL(disable_implicit_copy<Binding<Constraint>>(blc));
}

Production:

>>> overload_info(bc)
overload: T&&

>>> overload_info<Binding<Constraint>>(bc)
overload: const Binding<Constraint>&

>>> overload_info<Binding<LinearConstraint>>(blc)
overload: const Binding<LinearConstraint>&

>>> overload_info<Binding<Constraint>>(blc)
implicit copy: Binding<LinearConstraint>  ->  Binding<Constraint>
overload: Binding<Constraint>&&

>>> disable_implicit_copy<Binding<Constraint>>(bc)
[valid] overload: const Binding<Constraint>&

Erreur (avec clang-3.9 Dans bazel, lorsque la ligne incriminée n'est pas commentée):

cpp_quick/prevent_implicit_conversion.cc:116:8: error: call to deleted function 'disable_implicit_copy'
  CALL(disable_implicit_copy<Binding<Constraint>>(blc));
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Code source complet: prevent_implicit_conversion.cc

1
Eric Cousineau

J'ai d'abord essayé l'approche de PiotrNycz (pour C++ 03, que je suis obligé d'utiliser pour un projet), puis j'ai essayé de trouver une approche plus générale et j'ai trouvé cette ForcedType<T> classe de modèle.

template <typename T>
struct ForcedType {
    ForcedType(T v): m_v(v) {}
    operator T&() { return m_v; }
    operator const T&() const { return m_v; }

private:
    template <typename T2>
    ForcedType(T2);

    T m_v;
};

template <typename T>
struct ForcedType<const T&> {
    ForcedType(const T& v): m_v(v) {}
    operator const T&() const { return m_v; }

private:
    template <typename T2>
    ForcedType(const T2&);

    const T& m_v;
};

template <typename T>
struct ForcedType<T&> {
    ForcedType(T& v): m_v(v) {}
    operator T&() { return m_v; }
    operator const T&() const { return m_v; }

private:
    template <typename T2>
    ForcedType(T2&);

    T& m_v;
};

Si je ne me trompe pas, ces trois spécialisations devraient couvrir tous les cas d'utilisation courants. Je ne sais pas si une spécialisation pour rvalue-reference (à partir de C++ 11) est réellement nécessaire ou si la valeur par valeur est suffisante.

On l'utiliserait ainsi, dans le cas d'une fonction à 3 paramètres dont le 3ème paramètre ne permet pas les conversions implicites:

function(ParamType1 param1, ParamType2 param2, ForcedType<ParamType3> param3);
0
Fabio A.