Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which is better: returning tuple or passing arguments to function as references?

Tags:

c++

function

I created code where I have two functions returnValues and returnValuesVoid. One returns tuple of 2 values and other accept argument's references to the function.

#include <iostream>
#include <tuple>

std::tuple<int, int> returnValues(const int a, const int b) {
    return std::tuple(a,b);
}

void returnValuesVoid(int &a,int &b) {
    a += 100;
    b += 100;
}

int main() {
    auto [x,y] = returnValues(10,20);

    std::cout << x ;
    std::cout << y ;

    int a = 10, b = 20;
    returnValuesVoid(a, b);

    std::cout << a ;
    std::cout << b ;
}

I read about http://en.cppreference.com/w/cpp/language/structured_binding which can destruct tuple to auto [x,y] variables.

Is auto [x,y] = returnValues(10,20); better than passing by references? As I know it's slower because it does have to return tuple object and reference just works on orginal variables passed to function so there's no reason to use it except cleaner code.

As auto [x,y] is since C++17 do people use it on production? I see that it looks cleaner than returnValuesVoid which is void type and but does it have other advantages over passing by reference?

like image 557
A. Dziedziczak Avatar asked Dec 13 '17 10:12

A. Dziedziczak


1 Answers

Look at disassemble (compiled with GCC -O3):

It takes more instruction to implement tuple call.

0000000000000000 <returnValues(int, int)>:
   0:   83 c2 64                add    $0x64,%edx
   3:   83 c6 64                add    $0x64,%esi
   6:   48 89 f8                mov    %rdi,%rax
   9:   89 17                   mov    %edx,(%rdi)
   b:   89 77 04                mov    %esi,0x4(%rdi)
   e:   c3                      retq   
   f:   90                      nop

0000000000000010 <returnValuesVoid(int&, int&)>:
  10:   83 07 64                addl   $0x64,(%rdi)
  13:   83 06 64                addl   $0x64,(%rsi)
  16:   c3                      retq   

But less instructions for the tuple caller:

0000000000000000 <callTuple()>:
   0:   48 83 ec 18             sub    $0x18,%rsp
   4:   ba 14 00 00 00          mov    $0x14,%edx
   9:   be 0a 00 00 00          mov    $0xa,%esi
   e:   48 8d 7c 24 08          lea    0x8(%rsp),%rdi
  13:   e8 00 00 00 00          callq  18 <callTuple()+0x18> // call returnValues
  18:   8b 74 24 0c             mov    0xc(%rsp),%esi
  1c:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  23:   e8 00 00 00 00          callq  28 <callTuple()+0x28> // std::cout::operator<<
  28:   8b 74 24 08             mov    0x8(%rsp),%esi
  2c:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  33:   e8 00 00 00 00          callq  38 <callTuple()+0x38> // std::cout::operator<<
  38:   48 83 c4 18             add    $0x18,%rsp
  3c:   c3                      retq   
  3d:   0f 1f 00                nopl   (%rax)

0000000000000040 <callRef()>:
  40:   48 83 ec 18             sub    $0x18,%rsp
  44:   48 8d 74 24 0c          lea    0xc(%rsp),%rsi
  49:   48 8d 7c 24 08          lea    0x8(%rsp),%rdi
  4e:   c7 44 24 08 0a 00 00    movl   $0xa,0x8(%rsp)
  55:   00 
  56:   c7 44 24 0c 14 00 00    movl   $0x14,0xc(%rsp)
  5d:   00 
  5e:   e8 00 00 00 00          callq  63 <callRef()+0x23> // call returnValuesVoid
  63:   8b 74 24 08             mov    0x8(%rsp),%esi
  67:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  6e:   e8 00 00 00 00          callq  73 <callRef()+0x33> // std::cout::operator<<
  73:   8b 74 24 0c             mov    0xc(%rsp),%esi
  77:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  7e:   e8 00 00 00 00          callq  83 <callRef()+0x43> // std::cout::operator<<
  83:   48 83 c4 18             add    $0x18,%rsp
  87:   c3                      retq   

I don't think there is any considerable performance different, but the tuple one is more clear, more readable.

Also tried inlined call, there is absolutely no different at all. Both of them generate exactly the same assemble code.

0000000000000000 <callTuple()>:
   0:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
   7:   48 83 ec 08             sub    $0x8,%rsp
   b:   be 6e 00 00 00          mov    $0x6e,%esi
  10:   e8 00 00 00 00          callq  15 <callTuple()+0x15>
  15:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  1c:   be 78 00 00 00          mov    $0x78,%esi
  21:   48 83 c4 08             add    $0x8,%rsp
  25:   e9 00 00 00 00          jmpq   2a <callTuple()+0x2a> // TCO, optimized way to call a function and also return
  2a:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

0000000000000030 <callRef()>:
  30:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  37:   48 83 ec 08             sub    $0x8,%rsp
  3b:   be 6e 00 00 00          mov    $0x6e,%esi
  40:   e8 00 00 00 00          callq  45 <callRef()+0x15>
  45:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  4c:   be 78 00 00 00          mov    $0x78,%esi
  51:   48 83 c4 08             add    $0x8,%rsp
  55:   e9 00 00 00 00          jmpq   5a <callRef()+0x2a> // TCO, optimized way to call a function and also return
like image 185
Zang MingJie Avatar answered Sep 27 '22 21:09

Zang MingJie