Quel est le but de std :: make_pair par rapport au constructeur de std :: pair?


180

Quel est le but de std::make_pair?

Pourquoi ne pas le faire std::pair<int, char>(0, 'a')?

Y a-t-il une différence entre les deux méthodes?


6
En C ++ 11, vous pouvez presque entièrement vous passer de make_pair. Voyez ma réponse .
PlagueHammer

2
En C ++ 17, std::make_pairest redondant. Il y a une réponse ci-dessous qui détaille cela.
Drew Dormann

Réponses:


165

La différence est qu'avec std::pairvous devez spécifier les types des deux éléments, alors que vous std::make_paircréerez une paire avec le type des éléments qui lui sont passés, sans que vous ayez besoin de le dire. C'est ce que je pourrais tirer de divers documents de toute façon.

Voir cet exemple sur http://www.cplusplus.com/reference/std/utility/make_pair/

pair <int,int> one;
pair <int,int> two;

one = make_pair (10,20);
two = make_pair (10.5,'A'); // ok: implicit conversion from pair<double,char>

Mis à part le bonus de conversion implicite de celui-ci, si vous n'utilisez pas make_pair, vous devrez le faire

one = pair<int,int>(10,20)

à chaque fois que vous en attribuiez un, ce qui serait ennuyeux avec le temps ...


1
En fait, les types doivent être déduits au moment de la compilation sans qu'il soit nécessaire de les spécifier.
Chad

@Tor Ouais, je sais comment utiliser les deux, j'étais juste curieux de savoir s'il y avait une raison à cela std::make_pair. Apparemment, c'est juste pour des raisons de commodité.

@Jay Il semblerait que oui.
Tor Valamo

15
Je pense que vous pouvez le faire de one = {10, 20}nos jours, mais je n'ai pas de compilateur C ++ 11 à portée de main pour le vérifier.
MSalters

6
Notez également que cela make_pairfonctionne avec les types sans nom, y compris les structures, les unions, les lambdas et autres doodads.
Mooing Duck le

35

Comme @MSalters a répondu ci-dessus, vous pouvez maintenant utiliser des accolades pour le faire en C ++ 11 (je viens de vérifier cela avec un compilateur C ++ 11):

pair<int, int> p = {1, 2};

28

Les arguments du modèle de classe n'ont pas pu être déduits du constructeur avant C ++ 17

Avant C ++ 17, vous ne pouviez pas écrire quelque chose comme:

std::pair p(1, 'a');

car cela inférerait les types de modèles à partir des arguments du constructeur.

C ++ 17 rend cette syntaxe possible, et donc make_pairredondante.

Avant C ++ 17, std::make_pairnous permettait d'écrire du code moins détaillé:

MyLongClassName1 o1;
MyLongClassName2 o2;
auto p = std::make_pair(o1, o2);

au lieu des plus verbeux:

std::pair<MyLongClassName1,MyLongClassName2> p{o1, o2};

qui répète les types, et peut être très long.

L'inférence de type fonctionne dans ce cas pré-C ++ 17 car ce make_pairn'est pas un constructeur.

make_pair équivaut essentiellement à:

template<class T1, class T2>
std::pair<T1, T2> my_make_pair(T1 t1, T2 t2) {
    return std::pair<T1, T2>(t1, t2);
}

Le même concept s'applique à insertervs insert_iterator.

Voir également:

Exemple minimal

Pour rendre les choses plus concrètes, on peut observer le problème au minimum avec:

main.cpp

template <class MyType>
struct MyClass {
    MyType i;
    MyClass(MyType i) : i(i) {}
};

template<class MyType>
MyClass<MyType> make_my_class(MyType i) {
    return MyClass<MyType>(i);
}

int main() {
    MyClass<int> my_class(1);
}

puis:

g++-8 -Wall -Wextra -Wpedantic -std=c++17 main.cpp

compile joyeusement, mais:

g++-8 -Wall -Wextra -Wpedantic -std=c++14 main.cpp

échoue avec:

main.cpp: In function int main()’:
main.cpp:13:13: error: missing template arguments before my_class
     MyClass my_class(1);
             ^~~~~~~~

et nécessite au contraire de travailler:

MyClass<int> my_class(1);

ou l'assistant:

auto my_class = make_my_class(1);

qui utilise une fonction régulière au lieu d'un constructeur.

Différence pour `std :: reference_wrapper

Ce commentaire mentionne que std::make_pairdéballestd::reference_wrapper alors que le constructeur ne le fait pas, c'est donc une différence. Exemple TODO.

Testé avec GCC 8.1.0, Ubuntu 16.04 .


1
"C ++ 17 rend cette syntaxe possible, et donc make_pair redondant." - Pourquoi n'est-ce std::make_pairpas devenu obsolète dans C ++ 17?
andreee

@andreee Je ne suis pas sûr, la raison possible est que cela ne crée aucun problème, donc pas besoin de casser l'ancien code? Mais je ne suis pas familier avec le raisonnement du comité C ++, envoyez-moi un ping si vous trouvez quelque chose.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
Une chose utile que j'ai rencontrée est que la possibilité de spécifier les types avec std :: make_pair <T1, T2> (o1, o2) empêche l'utilisateur de faire l'erreur de passer des types o1 ou o2 qui ne peuvent pas être implicitement cast en T1 ou T2. Par exemple, passer un nombre négatif à un int non signé. -Wsign-conversion -Werror n'attrapera pas cette erreur avec le constructeur std :: pair en c ++ 11 mais il interceptera l'erreur si std :: make_pair est utilisé.
conchoecia

make_pairdéballe les wrappers de référence, donc c'est différent de CTAD en fait.
LF

26

Il n'y a aucune différence entre l'utilisation make_pairet l'appel explicite du pairconstructeur avec des arguments de type spécifiés. std::make_pairest plus pratique lorsque les types sont détaillés car une méthode de modèle a une déduction de type basée sur ses paramètres donnés. Par exemple,

std::vector< std::pair< std::vector<int>, std::vector<int> > > vecOfPair;
std::vector<int> emptyV;

// shorter
vecOfPair.push_back(std::make_pair(emptyV, emptyV));

 // longer
vecOfPair.push_back(std::pair< std::vector<int>, std::vector<int> >(emptyV, emptyV));

21

Il convient de noter qu'il s'agit d'un idiome courant dans la programmation de modèles C ++. Il est connu sous le nom d'idiome Object Generator, vous pouvez trouver plus d'informations et un bel exemple ici .

Modifier Comme quelqu'un l'a suggéré dans les commentaires (supprimés depuis), ce qui suit est un extrait légèrement modifié du lien en cas de rupture.

Un générateur d'objets permet la création d'objets sans spécifier explicitement leurs types. Il est basé sur une propriété utile des modèles de fonction que les modèles de classe n'ont pas: Les paramètres de type d'un modèle de fonction sont déduits automatiquement de ses paramètres réels. std::make_pairest un exemple simple qui renvoie une instance du std::pairmodèle en fonction des paramètres réels de la std::make_pairfonction.

template <class T, class U>
std::pair <T, U> 
make_pair(T t, U u)
{
  return std::pair <T, U> (t,u);
}

2
@duck En fait &&depuis C ++ 11.
Justme0

5

make_pair crée une copie supplémentaire sur le constructeur direct. Je saisis toujours mes paires pour fournir une syntaxe simple.
Cela montre la différence (exemple de Rampal Chaudhary):

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair( 1, sample) );
    //map.insert( std::pair<int,Sample>( 1, sample) );
    return 0;
}

4
Je suis presque sûr que la copie supplémentaire sera élidée dans tous les cas, si les paramètres d'optimisation du compilateur sont suffisamment élevés.
Björn Pollex

1
Pourquoi voudriez-vous jamais compter sur les optimisations du compilateur pour l'exactitude?
sjbx

J'obtiens les mêmes résultats avec les deux versions, et avec std::movejuste à l'intérieur insertet / ou autour de quoi ferait référence sample. Ce n'est que lorsque je passe std::map<int,Sample>à std::map<int,Sample const&>cela que je réduis le nombre d'objets construits, et seulement lorsque je supprime le constructeur de copie que j'élimine toutes les copies (évidemment). Après avoir apporté ces deux modifications, mon résultat comprend un appel au constructeur par défaut et deux appels au destructeur pour le même objet. Je pense que je dois manquer quelque chose. (g ++ 5.4.1, c ++ 11)
John P

FWIW Je suis d'accord pour dire que l'optimisation et l'exactitude devraient être complètement indépendantes, car c'est exactement le type de code que vous écrivez pour vérifier la cohérence après que différents niveaux d'optimisation produisent des résultats incohérents. En général, je recommanderais emplaceplutôt que insertsi vous construisiez simplement une valeur à insérer immédiatement (et vous ne voulez pas d'instances supplémentaires.) Ce n'est pas mon domaine d'expertise, si je peux même dire que j'en ai un, mais le copier / déplacer la sémantique introduite par C ++ 11 m'a beaucoup aidé.
John P

Je crois que je rencontre exactement le même problème et après avoir débogué pendant environ toute la soirée, je suis finalement arrivé ici.
lllllllllllll

1

à partir de c ++ 11, utilisez simplement une initialisation uniforme pour les paires. Donc au lieu de:

std::make_pair(1, 2);

ou

std::pair<int, int>(1, 2);

juste utiliser

{1, 2};

{1, 2}peut être utilisé pour initialiser une paire, mais ne s'engage pas pour la paire de types. À- dire lors de l' utilisation automatique , vous devez vous engager à un type sur l'ERS: auto p = std::pair{"Tokyo"s, 9.00};.
Markus
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.