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
Code Exchange
  • Technologies
  • More
Code Exchange
Blog modern C++ modules: convert existing example from .h to module
  • Blog
  • Forum
  • Documents
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Code Exchange to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 8 Feb 2025 2:39 PM Date Created
  • Views 721 views
  • Likes 5 likes
  • Comments 2 comments
  • Modern C++
  • c++26
  • c++23
  • c++
  • c++20
  • modules
Related
Recommended

modern C++ modules: convert existing example from .h to module

Jan Cumps
Jan Cumps
8 Feb 2025

I'm experimenting with new C++ constructs. 

In  C++ callbacks and templates , I developed a generic object oriented callback handler using templates.
In modern C++ modules I tried out an example that uses C++ modules.
This post brings those two together. I convert my callback design (a single .h file) into a module (a single .cpp file).

You need a recent compiler, and tell it that you use modern C++ constructs.
In this blog I use GCC14, and give it these parameters to enable modules:
-std=c++26  -fmodules-ts

The traditional header file approach

The original design, using header file looked like this:

#ifndef CALLBACKMANAGER_H_
#define CALLBACKMANAGER_H_

template <typename R, typename... Args>
  requires std::is_void<R>::value || std::is_arithmetic_v<R>

class Callback {
public:
	Callback() : callback_(nullptr){}

	inline void set(std::function<R(Args... args)> callback) {
	    callback_ = callback;
	}

// ...
};


#endif /* CALLBACKMANAGER_H_ */

and was used like this (an example that uses a class method as callback. The design can also call back functions, static methods, lambdas and other mechanisms)  :

#include "callbackmanager.h"

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

int main() {

    int a = 4;
    int b = 5;

    MyClass myClass;

    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.call(a, b);
    printf("Value: %i\n", o);
	fflush(stdout);

// ...

What you notice here:

  • the callback functionality is in a header file
  • the header file has the usual include guards at start and end (#ifndef ...)
  • the client file includes the header file

The module approach

The callback class now sits in a .cpp source file:

module;

export module callbackmanager;

export template <typename R, typename... Args>
  requires std::is_void<R>::value || std::is_arithmetic_v<R>

class Callback {
public:
	Callback() : callback_(nullptr){}

	inline void set(std::function<R(Args... args)> callback) {
	    callback_ = callback;
	}
// ...
};

and the file that uses it, imports the module instead of including the header.

import callbackmanager;

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

int main() {

    int a = 4;
    int b = 5;

    MyClass myClass;

    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.call(a, b);
    printf("Value: %i\n", o);
	fflush(stdout);

// ...

Differences:

  • the callback design sits in a source file. It creates the module, and exports the accessible module functionality.
  • no include guards anymore. They are not needed
  • the client file imports the module

That's it. 

Full source

callbackmanager.cpp

/*
 * callbackmanager.cpp
 *
 *  Created on: 16 jun. 2024
 *      Author: jancu
 */

 module;

 #include <functional>
 
 export module callbackmanager;
 
 namespace callbackmanager {
 
 export template <typename R, typename... Args>
  requires std::is_void<R>::value || std::is_arithmetic_v<R>
class Callback {
	using callbackfunction_t = std::function<R(Args...)>;	
public:
	Callback() : callback_(nullptr), isSet(false){}

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

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

	/*
	 * R can either be an arithmetic type, or void
	 */
	inline R call(Args... args) {
		if constexpr (std::is_void<R>::value) {
			if (!isSet) {
				return;
			}
			(callback_)(args...);
		}

		if constexpr (! std::is_void<R>::value) {
			if (!isSet) {
				return 0; // R can only be a arithmetic type. 0 should work as default.
			}
			return (callback_)(args...);
		}
	}

	inline bool is_set() {
		return isSet;		
	}

private:
	callbackfunction_t callback_;
	bool isSet;
};

} // namespace callbackmanager

callback_example.cpp (showing a number of examples that use the callback as OO or functional mechanism)

// https://community.element14.com/products/devtools/software/f/forum/54719/c-callbacks-and-templates


#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;
}

int main() {

    int a = 4;
    int b = 5;

	{ // scenario: call object method
    MyClass myClass;

    callbackmanager::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.call(a, b);
    printf("Value: %i\n", o);
	fflush(stdout);
	}

	{ // scenario: call static method
	callbackmanager::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.call(a, b);
    printf("Value: %i\n", o);
	fflush(stdout);
	}

	{ // scenario: call C function
	callbackmanager::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.call(a, b);
    printf("Value: %i\n", o);
	fflush(stdout);
	}

	{ // scenario: call pure lambda
	callbackmanager::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.call(a, b);
    printf("Value: %i\n", o);
	fflush(stdout);
	}

	{ // scenario: return a bool
	callbackmanager::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.call(a, b) ? "true" : "false");
	fflush(stdout);
	}

	{ // scenario: use void
	callbackmanager::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
	callbackmanager::Callback<void> cb;
    // Use a lambda to execute anonymous C code
    cb.set([]() {
        printf("void with no parameters\n");
    	fflush(stdout);
        return;
    });
    cb.call();
	}

	{ // scenario: use void, and a const std::string reference
	callbackmanager::Callback<void, const std::string&> cb;
    // Use a lambda to execute anonymous C code
    cb.set([](const std::string& s) {
        printf(s.c_str());
    	fflush(stdout);
        return;
    });
    cb.call("hello, world!\r\n");
	}

	{ // scenario: use void, and a const std::string reference
	std::string updateme;
	callbackmanager::Callback<void, std::string&> cb;
    // Use a lambda to execute anonymous C code
    cb.set([](std::string& s) {
    	s.assign("hello, world!\r\n");
        return;
    });
    cb.call(updateme);
    printf(updateme.c_str());
	fflush(stdout);
	}

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

}

cmake file

cmake_minimum_required(VERSION 3.28)

project(callbackmanager C CXX ASM)

# GoogleTest requires at least C++14
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmodules-ts -fcommon")
# set(CMAKE_EXE_LINKER_FLAGS "-Wl,-allow-multiple-definition")

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

enable_testing()

add_executable(${CMAKE_PROJECT_NAME}_example)
target_sources(${CMAKE_PROJECT_NAME}_example
        PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/example/callback_examples.cpp
)
target_sources(${CMAKE_PROJECT_NAME}_example
        PUBLIC
        FILE_SET cxx_modules TYPE CXX_MODULES FILES
        ${CMAKE_CURRENT_SOURCE_DIR}/callbackmanager.cpp
)

target_link_libraries( ${CMAKE_PROJECT_NAME}_example
)

add_executable(${CMAKE_PROJECT_NAME}_test)
target_sources(${CMAKE_PROJECT_NAME}_test
        PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/test/callbackmanager_test.cpp
)
target_sources(${CMAKE_PROJECT_NAME}_test
        PUBLIC
        FILE_SET cxx_modules TYPE CXX_MODULES FILES
        ${CMAKE_CURRENT_SOURCE_DIR}/callbackmanager.cpp
)

target_link_libraries( ${CMAKE_PROJECT_NAME}_test

        GTest::gtest_main
        GTest::gmock_main
)

include(GoogleTest)
gtest_discover_tests(${CMAKE_PROJECT_NAME}_test)

Thank you for reading.

  • Sign in to reply
  • DAB
    DAB 7 months ago

    Nice post Jan.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps 7 months ago

    I used an object's method as callback handler in the example. But you can as well use a standard c type function. Here's an example

    int functionHandler(const int& num1, const int& num2) {
        return num1 + num2;
    }
    
    int main() {
    
        int a = 4;
        int b = 5;
    
    	// scenario: call C function
    	callbackmanager::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.call(a, b);
        printf("Value: %i\n", o);
    	fflush(stdout);

    It calls function functionHandler(). 

    You an also use it to call plain c code that's not in a function (a lambda). See all options in  C++ callbacks and templates: make the Callback class call an object member, a static member, a C function and a pure lambda function .

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
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