License : Creative Commons Attribution 4.0 International (CC BY-NC-SA 4.0)
Copyright : Hervé Frezza-Buet, CentraleSupelec
Last modified : April 19, 2024 10:22
Link to the source : functions.md

Table of contents

Functions

Declaration and functional type

Some functions may return type void, they are only procedures. No return statement is required for them, the instruction return; only breaks the execution in this case.

/* classical (before c++11) */
A    f(B arg1, C arg2)      {A res; /* body */ return res;}

/* since c++11 */
auto f(B arg1, C arg2) -> A {A res; /* body */ return res;}

/* since c++14 */
auto f(B arg1, C arg2)      {A res; /* body */ return res;}

// The type of f is
//   A (B, C)

The STL, since C++11, adds higher level types and assorted tools in order to handle functions.

#include <functional>

A a; B b; C c;
auto f(B arg1, C arg2) -> A {A res; /* body */ return res;}

// The basic C way (not recommended)
A (*ptr)(B, C) = f; // p stores the adress of f in the text.
a = (*ptr)(b, c);   // similar to a = f(b, c).

std::function<A (B, C)> g = f; // A nicer way...
a = g(b, c);                   // similar to a = f(b, c).

Manipulation of functions

Here is an example involving some of the tools. Functions are hanled as any kind of value (passed as arguments, returned, …).

#include <functional>
#include <string>
#include <iostream>
 
using namespace std::placeholders;
 
void show(const std::string& a, const std::string& b, const std::string& c) {
  std::cout << a << ' ' 
	    << b << ' ' 
	    << c << std::endl;
}

void set(int& variable, int value) {variable = value;}
 
int main() {
  auto x = std::bind(show,         _1, _2, _3);
  auto y = std::bind(show,         _3, _1, _2);
  auto z = std::bind(show,"hello", _2, _1);
  
  std::function<void (std::string)> quote = std::bind(show, "\"", _1, "\"");
     
  x("one", "two", "three");      // Output : one two three
  y("one", "two", "three");      // Output : three one two
  z("one", "two");               // Output : hello two one
  quote("This is a quotation");  // Output : " This is a quotation "
  std::invoke(z, "me", "it's");  // Output : hello it's me

  int i = 25;
  auto raz  = std::bind(set,          _1,  0);
  auto seti = std::bind(set, std::ref(i), _1);
  
  raz(i); seti(32); std::cout << "i = " << i << std::endl; // Output : i = 32
  
  return 0;
}

Method pointers

You need to know what inheritance and virtual methods are to understand this.

#include <functional>
#include <iostream>
#include <list>

namespace gui {  // should be in widget.hpp
  class Button {
  private:
    std::list<std::function<void ()>> callbacks;
    
  public:
    Button()                         = default;
    Button(const Button&)            = default;
    Button& operator=(const Button&) = default;
    
    void operator+=(const std::function<void ()>& cb) {callbacks.emplace_back(cb);}
    void click() {for(auto& cb : callbacks) cb();}
  };
}

// Comments starting with ### concern stuff related to inheritance.
// Ignore those points if you are not familiar with this concept yet.
struct MyInterface { 
  double value1, value2;
  gui::Button b_raise1, b_raise2, b_lower1, b_lower2, b_raz1, b_raz2;
  gui::Button b_raise, b_lower, b_raz;

  virtual void set1 (double i) {value1  = i;} // ### virtual is related to inheritance... 
  virtual void set2 (double i) {value2  = i;} // ### virtual is related to inheritance...
  void incr1(double i) {set1(value1 + i);}
  void incr2(double i) {set2(value2 + i);}

  MyInterface() : value1(0), value2(0) {
    b_raise1 += std::bind(&MyInterface::incr1, std::ref(*this),  1);
    b_lower1 += std::bind(&MyInterface::incr1, std::ref(*this), -1);
    b_raz1   += std::bind(&MyInterface::set1,  std::ref(*this),  0); // ### virtial set1 handled here...
    
    b_raise2 += std::bind(&MyInterface::incr2, std::ref(*this),  1);
    b_lower2 += std::bind(&MyInterface::incr2, std::ref(*this), -1);
    b_raz2   += std::bind(&MyInterface::set2,  std::ref(*this),  0); // ### virtial set2 handled here...
    
    b_raise  += std::bind(&MyInterface::incr1, std::ref(*this),  1);
    b_lower  += std::bind(&MyInterface::incr1, std::ref(*this), -1);
    b_raz    += std::bind(&MyInterface::set1,  std::ref(*this),  0); // ### virtial set1 handled here...
    b_raise  += std::bind(&MyInterface::incr2, std::ref(*this),  1);
    b_lower  += std::bind(&MyInterface::incr2, std::ref(*this), -1);
    b_raz    += std::bind(&MyInterface::set2,  std::ref(*this),  0); // ### virtial set2 handled here...
  }
};

std::ostream& operator<<(std::ostream& os, MyInterface& inter) {
  os << '{' << inter.value1 << ", " << inter.value2 << '}';
  return os;
}

void user_interaction(MyInterface& inter) {
  inter.b_raise.click();
  inter.b_raise.click();
  inter.b_lower2.click();
  inter.b_raise1.click();
  inter.b_raise.click();
  std::cout << ">>> " << inter << std::endl;
  inter.b_raz1.click();
  std::cout << ">>> " << inter << std::endl;
}

// ### We inherit from MyInterface to have a verbose one.
struct MyVerboseInterface : public MyInterface {
  MyVerboseInterface() = default;
  virtual void set1 (double i) {MyInterface::set1(i), std::cout << "V1 = " << i << std::endl;} 
  virtual void set2 (double i) {MyInterface::set2(i), std::cout << "V2 = " << i << std::endl;} 
};

int main(int argc, char* argv[]) {
  MyInterface inter;
  user_interaction(inter);

  std::cout << "------------" << std::endl;
  
  MyVerboseInterface vinter;
  user_interaction(vinter);

  return 0;
}

/* Output: */

Lambda functions

A nice syntax for functors

#include <iostream>
#include <iomanip>
#include <functional>

struct IsInBound_byval {
  double lower;
  double upper;
  IsInBound_byval(double lower, double upper) : lower(lower), upper(upper) {}
  bool operator()(double x) {return lower <= x && x < upper;}
};

struct IsInBound_byref {
  double& lower;
  double& upper;
  IsInBound_byref(double& lower, double& upper) : lower(lower), upper(upper) {}
  bool operator()(double x) {return lower <= x && x < upper;}
};

bool in_bound(double x, double lower, double upper) {return lower <= x && x < upper;}

using namespace std::placeholders;

int main(int argc, char* argv[]) {
  double lower = 0;
  double upper = 1;

  IsInBound_byval test_val(lower,upper);
  auto lambda_val = [lower, upper](double x) -> bool {return lower <= x && x < upper;};
  auto   bind_val = std::bind(in_bound, _1, lower, upper);
  std::cout << std::boolalpha
	    <<                                                           test_val (0.5)   << std::endl // Output : true
	    <<                                                         lambda_val (0.5)   << std::endl // Output : true
	    <<                                                           bind_val (0.5)   << std::endl // Output : true
	    <<                                                                               std::endl
	    <<                                      IsInBound_byval(lower, upper) (0.5)   << std::endl // Output : true
	    << [lower, upper](double x) -> bool {return lower <= x && x < upper;} (0.5)   << std::endl // Output : true
	    <<                              std::bind(in_bound, _1, lower, upper) (0.5)   << std::endl // Output : true
	    << std::endl;

  IsInBound_byref test_ref(lower,upper);
  auto lambda_ref = [&lower, &upper](double x) -> bool {return lower <= x && x < upper;};
  auto   bind_ref = std::bind(in_bound, _1, std::ref(lower), std::ref(upper));
  std::cout << std::boolalpha
	    <<                                                             test_ref (0.5) << std::endl // Output : true
	    <<                                                           lambda_ref (0.5) << std::endl // Output : true
	    <<                                                             bind_ref (0.5) << std::endl // Output : true
	    <<                                                                               std::endl
	    <<                                        IsInBound_byref(lower, upper) (0.5) << std::endl // Output : true
	    << [&lower, &upper](double x) -> bool {return lower <= x && x < upper;} (0.5) << std::endl // Output : true
	    <<            std::bind(in_bound, _1, std::ref(lower), std::ref(upper)) (0.5) << std::endl // Output : true
	    << std::endl;

  lower = 0.75;
  std::cout << std::boolalpha
	    <<   test_val (0.5) << std::endl // Output : true
	    << lambda_val (0.5) << std::endl // Output : true
	    <<   bind_val (0.5) << std::endl // Output : true
	    <<                     std::endl
	    <<   test_ref (0.5) << std::endl // Output : false
	    << lambda_ref (0.5) << std::endl // Output : false
	    <<   bind_ref (0.5) << std::endl // Output : false
	    << std::endl;
  return 0;
}

Refinements

struct A {
  int x;
  void f(int y) {
    auto incr = [this](double arg) ->double {return arg + x;}; // this is captured, no need to write this->x
  }
};

// No need for return type (-> bool here) if it can be deduced
auto is_positive = [](int x) {return x >= 0;};

// Variables can be defined in the capture
auto f = [bound = l.max_value()](double x) {return x < bound;};
auto f = [aa = std::move(a)](int x) -> bool {/* We work with aa, i.e. a copy of a that has taken its memory... */};

// Types can be let unknown and instanciated when used in some code
// (template lambda)
auto f = [...](auto a, auto& b) {...}

Smart examples

#include <algorithm>
#include <vector>
#include <iostream>

// Find the closest element in a collection.

struct Point {double x = 0; double y = 0;};
inline double d2(const Point& A, const Point& B) {double dx = A.x - B.x; double dy = A.y - B.y; return dx * dx + dy * dy;}
std::ostream& operator<<(std::ostream& os, const Point& p) {os << '(' << p.x << ", " << p.y << ')'; return os;}

int main(int argc, char* argv[]) {
  std::vector<Point> pts = {{1, 2}, {3, 4}, {5, 6}, {7, 8}};
  Point x {8, 2};
  
  // usage : it = std::min_element(begin, end, element_lesser_than_function)
  auto argmin = std::min_element(pts.begin(), pts.end(),
				 [&x /* avoids a copy */](const auto& elem1, const auto& elem2) {
				   return d2(elem1, x) < d2(elem2, x); // This should returns the meaning of elem1 < elem2.
				 });
  auto& closest_point = *argmin;
  std::cout << "The closest point to " << x << " in pts is " << closest_point << std::endl;
  // Output : The closest point to (8, 2) in pts is (5, 6)
  
  return 0;
}

The std::range library

Since C++20, it combines functions and iteration and provide a smart formulation of many kind of iterations. Iteration are lazy, so they can be infinite. The start is known (begin), but the ending my not exist. It it exist, the ending is not a specific value of the iterator, but a sentinel which may be of different type than the iterator.

C++20 rewrites the algorithms with (begin, sentinel) pairs for representing ranges, rather than (begin, end) pairs of iterators. The range library collections are of that kind.

#include <ranges>
#include <iostream>

bool greather_that_10(unsigned int i) {return i >= 10;          }
bool not_in_20_50    (unsigned int i) {return i < 20 || i >= 50;}
bool lower_than_60   (unsigned int i) {return i < 60;           }

int main(int argc, char* argv[]) {
  {
    std::cout << "Simple iteration" << std::endl;
    auto ints = std::views::iota((unsigned int)0);                // Applies ++ iteratively.
    auto ints_iter = ints.begin();
    for(int i = 0; i < 10; ++i) std::cout << *(ints_iter++) << ' ';
    std::cout << std::endl << std::endl;
    // Output : 0 1 2 3 4 5 6 7 8 9 
  }
  {
    std::cout << "Combined iteration" << std::endl;
    auto ints   = std::views::iota((unsigned int)0);              // Infinite serie, lazy.
    auto g10    = std::views::filter(ints, greather_that_10);     // Native syntax, infinite serie, lazy.
    auto ni2050 = g10 | std::views::filter(not_in_20_50);         // Nice syntax, infinite serie, lazy.
    auto l50    = ni2050 | std::views::take_while(lower_than_60); // finite serie (it reaches the end iterator), lazy.
    for(auto it = l50.begin(); it != l50.end(); ++it) std::cout << *(it) << ' ';
    std::cout << std::endl << std::endl;
    // Output : 10 11 12 13 14 15 16 17 18 19 50 51 52 53 54 55 56 57 58 59 
  }
  {
    std::cout << "Combined iteration with the for syntax" << std::endl;
    auto l50 = std::views::iota((unsigned int)0)
      | std::views::filter(greather_that_10)
      | std::views::filter(not_in_20_50)
      | std::views::take_while(lower_than_60);
    for(auto val : l50) std::cout << val << ' ';
    std::cout << std::endl;
    // Output : 10 11 12 13 14 15 16 17 18 19 50 51 52 53 54 55 56 57 58 59 

    // Or even better
    for(auto val
	  : std::views::iota((unsigned int)0)
	  | std::views::filter(greather_that_10)
	  | std::views::filter(not_in_20_50)
	  | std::views::take_while(lower_than_60)) std::cout << val << ' ';
    std::cout << std::endl;
    // Output : 10 11 12 13 14 15 16 17 18 19 50 51 52 53 54 55 56 57 58 59 

    // Or even better with lambdas
    for(auto val
	  : std::views::iota      ((unsigned int)0)
	  | std::views::filter    ([](auto i){return i >= 10;          })
	  | std::views::filter    ([](auto i){return i < 20 || i >= 50;})
	  | std::views::take_while([](auto i){return i < 60;           })) std::cout << val << ' ';
    std::cout << std::endl<< std::endl;
    // Output : 10 11 12 13 14 15 16 17 18 19 50 51 52 53 54 55 56 57 58 59 

  }
  {
    std::cout << "Reverse iteration" << std::endl;
    // This works with a finite range
    for(auto val
	  : std::views::iota      ((unsigned int)0)
	  | std::views::filter    ([](auto i){return i >= 10;          })
	  | std::views::filter    ([](auto i){return i < 20 || i >= 50;})
	  | std::views::take_while([](auto i){return i < 60;           })
	  | std::views::reverse)
      std::cout << val << ' ';
    std::cout << std::endl<< std::endl;
    // Output : 59 58 57 56 55 54 53 52 51 50 19 18 17 16 15 14 13 12 11 10 
  }
  {
    std::cout << "Counted iterations" << std::endl;
    // Makes an inifinite range finite by limitating the number of elements.
    // We introduce transforms here as well.
    auto range = std::views::iota((unsigned int)0)
      | std::views::filter       ([](auto i){return i >= 10;})
      | std::views::transform    ([](auto i){return i * i;  });
    for(auto val : std::views::counted(range.begin(), 13))
      std::cout << val << ' ';
    std::cout << std::endl<< std::endl;
    // Output : 100 121 144 169 196 225 256 289 324 361 400 441 484
  }

  return 0;
}

Many range operator exist and the list may increase with future versions of C++. Here is another example for free.

#include <ranges>
#include <vector>
#include <tuple>
#include <map>
#include <sstream>
#include <iostream>
#include <iterator>
#include <algorithm>

int main(int argc, char* argv[]) {
  std::string file_content = "Batman Bruce Wayne - Superman Clark Kent - Wonderwoman Diana Prince - Spiderman Peter Parker - Tornade Ororo Munroe";
  
  {
    std::istringstream file(file_content);
    for(const auto& word : std::ranges::istream_view<std::string>(file))
      std::cout << word << ", ";
    std::cout << std::endl << std::endl;
    // Output : Batman, Bruce, Wayne, -, Superman, Clark, Kent, -, Wonderwoman, ...
  }

  {
    std::map<std::string, std::string> secret_identity;
    std::istringstream file(file_content);
    // std::ranges::istream_view has a sentinel_type different from
    // iterator_type, and iterator_type do not comply with legacy
    // iterator. It can hardly be used with STL algorithms, for example.
    // To cope with this issue, we copy the content of the file
    // into a vector (not using std::copy), which breaks the
    // laziness...
    std::vector<std::string> dumped_words;
    auto out = std::back_inserter(dumped_words);
    for(auto& word : std::ranges::istream_view<std::string>(file)) *(out++) = std::move(word);
    for(const auto& hero : dumped_words | std::views::split("-")) {
      auto it = hero.begin();
      std::string nickname = std::move(*(it++));
      std::string identity = std::move(*(it++));
      while(it != hero.end()) identity += std::string(" ") + std::move(*(it++));
      secret_identity[std::move(nickname)] = std::move(identity);
    }

    for(const auto& key_value : secret_identity) {
      const auto& [nickname, identity] = key_value;
      std::cout << nickname << " is " << identity << std::endl;
    }
    std::cout << std::endl << std::endl;
    // Output :
    //  Batman is Bruce Wayne
    //  Spiderman is Peter Parker
    //  Superman is Clark Kent
    //  Tornade is Ororo Munroe
    //  Wonderwoman is Diana Prince
    
    std::cout << "We know";
    for(const auto& hero : std::views::keys(secret_identity)) std::cout << ' ' << hero;
    std::cout << std::endl;
    // Output : We know Batman Spiderman Superman Tornade Wonderwoman
  }
}
Hervé Frezza-Buet,