element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Software
  • Products
  • Dev Tools
  • Software
  • More
  • Cancel
Software
Forum C++ callbacks and templates: make the Callback class call an object member, a static member, a C function and a pure lambda function
  • Forum
  • Documents
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Software to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Forum Thread Details
  • Replies 1 reply
  • Subscribers 27 subscribers
  • Views 1104 views
  • Users 0 members are here
  • isr
  • interrupt
  • Modern C++
  • stl
  • lambda
  • OO
  • c++
  • callback
Related

C++ callbacks and templates: make the Callback class call an object member, a static member, a C function and a pure lambda function

Jan Cumps
Jan Cumps over 1 year ago

I developed a templated C++ callback manager:  C++ callbacks and templates . I claimed that it can call back 

  • a classic C function,
  • a lambda,
  • a static class method or
  • an object method,

and it supports these return types:

  • void
  • bool
  • number
  • objects (if the class has a default constructor and "is returnable".

But in that first post, I only showed an example where the method of an object was used as callback handler. In this post, I show all 4 scenarios. This time, I'm not showing the power of templates, but the power of lambda functions. 

As a refresher, here is the Callback manager. Unchanged since previous post.

callmanager.cpp

module;

#include <concepts>
#include <functional>

export module callbackmanager;

namespace callbackmanager {

// concept guards what types of return values we can handle
template<typename R>
concept Callbackable = 
	std::is_void<R>::value ||
	std::is_arithmetic_v<R> ||
	std::is_class_v<R>;

export template <Callbackable R, typename... Args>
class Callback {
	using callbackfunction_t = std::function<R(Args...)>;	
public:
	Callback() : callback_(nullptr), is_callback_set(false){}

	inline void set(callbackfunction_t callback) {
	    callback_ = callback;
		is_callback_set = true;
	}

	inline void unset() {
		callback_ = nullptr;
		is_callback_set = false;
	}

	[[deprecated("Use operator () instead.")]]
	inline R call(Args... args) {
		return *this(args...);
	}

	inline bool is_set() {
		return is_callback_set;		
	}

	/*
	 * R can be an arithmetic type, an object, or void
	 */
	inline R operator()(Args... args) {
		if constexpr (std::is_void<R>::value) {  // void
			if (!is_callback_set) {
				return;
			} else {
				(callback_)(args...);
			}
		} else if constexpr (std::is_class<R>::value) { // object
			if (!is_callback_set) {
				return R();
			} else {
				return (callback_)(args...);
			}
		} else { // not void nor object
			if (!is_callback_set) {
				return 0; // R can only be a arithmetic type. 0 should work as default.
			} else {
				return (callback_)(args...);
			}
		}
	}

private:
	callbackfunction_t callback_;
	bool is_callback_set;
};

} // namespace callbackmanager

And here is the code that exercises the 4 scenarios. Each scenario is surrounded by {}. That means that they run in their own little isolated scope, And they don't spoil each other.

First, some code that will be used in the test. They are 3 different types of functions / methods:

  • MyClass, now with a static and normal member function. So that we can test a scenario where we call an object member, and a scenario where we call a static class member.
  • classic C function functionHandler().

All 3 do the same thing. They return the sum of two integers.

#include <cstdio>
#include <string>
#include <typeinfo>

import callbackmanager;

class MyClass {
public:
      inline int handler(const int& num1, const int& num2) const {
          return num1 + num2;
      }
      static inline int staticHandler(const int& num1, const int& num2) {
          return num1 + num2;
      }
};

int functionHandler(const int& num1, const int& num2) {
    return num1 + num2;
}

Then the 4 tests:

int main() {

    int a = 4;
    int b = 5;

    Callback<int, const int&, const int&> cb;
    // Use a lambda to capture myClass and call the object method
    cb.set([&myClass](const int& num1, const int& num2) -> int {
        return myClass.handler(num1, num2);
    });

    int o = cb(a, b);
    printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout
	fflush(stdout);
	}

	{ // scenario: call static method
    Callback<int, const int&, const int&> cb;
    // Use a lambda to call the static method
    cb.set([](const int& num1, const int& num2) -> int {
        return MyClass::staticHandler(num1, num2);
    });

    int o = cb(a, b);
    printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout
	fflush(stdout);
	}

	{ // scenario: call C function
    Callback<int, const int&, const int&> cb;
    // Use a lambda to call the classic C function
    cb.set([](const int& num1, const int& num2) -> int {
        return functionHandler(num1, num2);
    });

    int o = cb(a, b);
    printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout
	fflush(stdout);
	}

	{ // scenario: call pure lambda
    Callback<int, const int&, const int&> cb;
    // Use a lambda to execute anonymous C code
    cb.set([](const int& num1, const int& num2) -> int {
        return num1 + num2;
    });

    int o  = cb(a, b);
    printf("Value: %i\n", o);
	fflush(stdout);
	}

	{ // scenario: return a bool
    Callback<bool, const int&, const int&> cb;
    // Use a lambda to execute anonymous C code
    cb.set([](const int& num1, const int& num2) -> bool {
        return num1 == num2;
    });

    printf("Value: %s\n", cb(a, b) ? "true" : "false");
	fflush(stdout);
	}

	{ // scenario: use void
    Callback<void, const int&, const int&> cb;
    // Use a lambda to execute anonymous C code
    cb.set([](const int& num1, const int& num2) {
        return;
    });
    cb(a, b);
	}
	
    { // scenario: return an object
    class return_class {
    public:
        return_class() : value_(0) {};
        int value_;
    };

	callbackmanager::Callback<return_class> cb;
    // Use a lambda to execute anonymous C code
    cb.set([]() -> return_class {
        return_class rc;
        rc.value_ = 1;
        return rc;
    });
    return_class ret = cb();
    printf("Value: %d\n", ret.value_ );
	fflush(stdout);
	}

	/*
	{ // scenario: use an unsupported (non-fundamental) type for return value R
	  // this will generate a compile error
    Callback<std::string, const int&, const int&> cb;
	}
}

edit: The template also allows void return values. Some more tests:

	{ // scenario: use void
    Callback<void, const int&, const int&> cb;
    // Use a lambda to execute anonymous C code
    cb.set([](const int& num1, const int& num2) {
        printf("void gets num1: %i, num2: %i\n", num1, num2);
    	fflush(stdout);
        return;
    });
    cb.call(a, b);
	}

	{ // scenario: use void, and no attributes
    Callback<void> cb;
    // Use a lambda to execute anonymous C code
    cb.set([]() {
        printf("void with no parameters\n");
    	fflush(stdout);
        return;
    });
    cb.call();
	}

Scenarios, in order of execution: (edit: this was written before I revised the code to support configurable return value. The test code above is adapted, but I didn't change the images below)

  1. call object method
    In this one, I create object myClass of type MyClass. Then pass its handle() method as callback.
    This was the scenario of the first post.
    image

  2. call static class method
    In this test, I don't declare an object of type MyClass. I pass the static MyClass::staticHandle()
    image

  3. classic C function
    Here, the callback gets a very common C function, named functionHandler(), as handler. 
    image

  4. a pure lambda.
    In this test, I pass an anonymous block of code as handler:
    return num1 + num2;
    image
  5. return an object. This is added on 09-mar-2025.Works as long as the object supports a default constructor and does not forbid copy / assign.
    image

In each test, the Callback object cb will call the code that's given to it in the set() call. All using the exact same little Callback template class. If you would set breakpoints, here is where the code would stop for each of the 4 cases:

image

edit: this was written before I replaced call() by operator ()..In your code, don't use cb.call(args), but cb(args). The test code in the blog is adapted, but I didn't change the images.

The test code is available as a Gist. 

  • Sign in to reply
  • Cancel
Parents
  • Jan Cumps
    Jan Cumps over 1 year ago

    Example program for a Pico:

    • a Led object knows how to toggle
    • the toggle method is set as callback 
    • when a rising edge interrupt occurs on GPIO 27 (where a button is attached), the callback is called.
      The usual bounce happens if you don't filter the signal, but for a demo it will do.

    #include "pico/stdlib.h"
    #include "hardware/gpio.h"
    
    #include <cstdio>
    #include <string>
    #include "callbackmanager.h"
    
    // button on pico eurocard
    #define BUTTON (27)
    
    class Led {
    public:
          void init() {
            gpio_init(PICO_DEFAULT_LED_PIN);
            gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
          }
          int toggle() {
            // toggle a led, then exit
            gpio_put(PICO_DEFAULT_LED_PIN, !gpio_get(PICO_DEFAULT_LED_PIN));
            return 0;
          }
    };
    
    Callback<int> cb_button;
    Led led;
    
    /*
    handle SDK HAL gpio callback.
    hand over to the Callback object
    */
    void gpio_hal_callback(uint gpio, uint32_t events) {
      if((gpio == BUTTON) && ((events & GPIO_IRQ_EDGE_RISE))) {
          // HAL reports we received a rising edge from the button
          // invoke callback
          cb_button.call();
      }
    }
    
    int main() {
        stdio_init_all();
        gpio_init(BUTTON);
        gpio_set_dir(BUTTON, GPIO_IN);
        gpio_set_pulls(BUTTON, true, false);
    
        led.init();
    
        cb_button.set([]() -> int {
            return led.toggle();
        });
    
        // enable SDK HAL interrupt for gpio
        gpio_set_irq_enabled_with_callback(BUTTON, GPIO_IRQ_EDGE_RISE, true, &gpio_hal_callback);
    
        while(true) {}
    }

    In this example, you see 2 variations on the previous examples:

    - the Callback template can also be used with 0 attributes.

    Callback<int> cb_button;

    In this case, the class will only allow that you set handlers that take no arguments (the int is the return value type).

    - I did not put anything in between the lambda []. Because the cb_button object is a global object. You don't have to capture global objects, because lambdas (like any code in your program) can access them. (It actually generates a compile warning if you try to capture a global variable for a lambda: capture of variable 'cb_button' with non-automatic storage duration).
    I'm deliberately simplifying the explanation here by calling this a global instead of variable with non-automatic storage. But I guess that if you know what non-automatic storage duration is, you don't need my explanation anyway Slight smile.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Cancel
Reply
  • Jan Cumps
    Jan Cumps over 1 year ago

    Example program for a Pico:

    • a Led object knows how to toggle
    • the toggle method is set as callback 
    • when a rising edge interrupt occurs on GPIO 27 (where a button is attached), the callback is called.
      The usual bounce happens if you don't filter the signal, but for a demo it will do.

    #include "pico/stdlib.h"
    #include "hardware/gpio.h"
    
    #include <cstdio>
    #include <string>
    #include "callbackmanager.h"
    
    // button on pico eurocard
    #define BUTTON (27)
    
    class Led {
    public:
          void init() {
            gpio_init(PICO_DEFAULT_LED_PIN);
            gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
          }
          int toggle() {
            // toggle a led, then exit
            gpio_put(PICO_DEFAULT_LED_PIN, !gpio_get(PICO_DEFAULT_LED_PIN));
            return 0;
          }
    };
    
    Callback<int> cb_button;
    Led led;
    
    /*
    handle SDK HAL gpio callback.
    hand over to the Callback object
    */
    void gpio_hal_callback(uint gpio, uint32_t events) {
      if((gpio == BUTTON) && ((events & GPIO_IRQ_EDGE_RISE))) {
          // HAL reports we received a rising edge from the button
          // invoke callback
          cb_button.call();
      }
    }
    
    int main() {
        stdio_init_all();
        gpio_init(BUTTON);
        gpio_set_dir(BUTTON, GPIO_IN);
        gpio_set_pulls(BUTTON, true, false);
    
        led.init();
    
        cb_button.set([]() -> int {
            return led.toggle();
        });
    
        // enable SDK HAL interrupt for gpio
        gpio_set_irq_enabled_with_callback(BUTTON, GPIO_IRQ_EDGE_RISE, true, &gpio_hal_callback);
    
        while(true) {}
    }

    In this example, you see 2 variations on the previous examples:

    - the Callback template can also be used with 0 attributes.

    Callback<int> cb_button;

    In this case, the class will only allow that you set handlers that take no arguments (the int is the return value type).

    - I did not put anything in between the lambda []. Because the cb_button object is a global object. You don't have to capture global objects, because lambdas (like any code in your program) can access them. (It actually generates a compile warning if you try to capture a global variable for a lambda: capture of variable 'cb_button' with non-automatic storage duration).
    I'm deliberately simplifying the explanation here by calling this a global instead of variable with non-automatic storage. But I guess that if you know what non-automatic storage duration is, you don't need my explanation anyway Slight smile.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Cancel
Children
No Data
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube