Modules in VC++ 2019 16.5

栏目: IT技术 · 发布时间: 4年前

内容简介:Modules are one of the bigest changes in C++20 but the compilers’ support for them is a work in progress. The Visual C++ compiler has experimental support for modules that can be enabled by using theA typical hello world application in C++ looks like this:

Modules are one of the bigest changes in C++20 but the compilers’ support for them is a work in progress. The Visual C++ compiler has experimental support for modules that can be enabled by using the /experimental:module and /std:c++latest switches. In this post, I will walk through the core of the functionality available in Visual Studio 2019 16.5.

A first example

A typical hello world application in C++ looks like this:

#include <iostream>
 
int main()
{
    std::cout << "Hello, World!\n";
}

How do we transform this code so that it uses modules? Just replace the #include preprocessor directive with an import directive.

import std.core;
 
int main()
{
    std::cout << "Hello, World!\n";
}

The std.core module provides most of the content of the C++ Standard Library. The library is modularized as follows:

  • std.regex : the content of header <regex>
  • std.filesystem : the content of header <filesystem>
  • std.memory : the content of header <memory>
  • std.threading : the contents of headers <atomic> , <condition_variable> , <future> , <mutex> , <shared_mutex> , <thread>
  • std.core the rest of the C++ Standard Library

To be able to use the modularized version of the Standard Library you must install the component called C++ Modules for v142 build tools shown in the following image:

Modules in VC++ 2019 16.5

When importing the Standard Library you should build with the /MD and /EHsc options.

To build the code snippet above, open a Developer Command Prompt from Visual Studio, and execute the following command:

cl /std:c++latest /EHsc /experimental:module /MD main.cpp

Now, if you run main.exe , you get the expected result:

Modules in VC++ 2019 16.5

Writing a module

Instead of just printing a greeting text in main() , we could get that text from a function. In the following example, this function called get_greeting_text() is exported from a module called greetings . This module is defined in an module interface unit called greetings.ixx .

The .ixx extension is required by the VC++ compiler for module interface units.

export module greetings;
 
import std.core;
 
export std::string get_greeting_text()
{
    return "Hello, World!\n";
}

The main.cpp file needs to change slightly to import the greetings module and invoke the get_greeting_text() function.

import std.core;
import greetings;
 
int main()
{
    std::cout << get_greeting_text() << '\n';
}

Now, you need to build both greetings.ixx and main.cpp . The following commands must be executed:

cl /std:c++latest /EHsc /experimental:module /MD /c greetings.ixx
cl /std:c++latest /EHsc /experimental:module /MD main.cpp greetings.obj

Let’s add more to the greetings module. In the following snippet, greeter is a class with an overloaded call operator that, when invoked, returns a random greeting text.

export module greetings;
 
import std.core;
 
export std::string get_greeting_text()
{
    return "Hello, World!";
}
 
export struct greeter
{
   constexpr static const char* hellos[] {"Hello", "Hi", "Hey"};
   std::string operator()()
   {
      return hellos[rand() % 3] + std::string{", World!"};
   }
};

In main.cpp we will have the following:

import std.core;
import greetings;
 
int main()
{   
    std::cout << get_greeting_text() << '\n';
    
    std::cout << greeter()() << '\n';
}

The commands for compiling this code remain the same. However, each time we execute the program now a different text will be printed to the console.

Composing a module from partitions

Modules can be split into partitions. Partitions help to organize the code of a module, especially if the module is large. Partitions are not exported as stand-alone units but as parts of a module interface unit.

To exemplify module partitions, let’s split the code of the greetings modules into two partitions: one that contains the free functions, called greetings-func and one that contains the classes, called greetings-types . These are also available in files with the extension .ixx. Here is how the look:

The content of greetings-func.ixx is:

export module greetings:func;
 
export const char* get_greeting_text()
{
    return "Hello, World!";
}

The content of greetings-types.ixx is:

module;
 
#include <cstdlib>
 
export module greetings:types;
 
import std.core;
 
export struct greeter
{
   constexpr static const char* hellos[] {"Hello", "Hi", "Hey"};
   std::string operator()()
   {
      return hellos[rand() % 3] + std::string{", World!"};
   }
};

The syntax for exporting a module partitions is export module <module-name>:<partition-name> . The rest is no different than regular module interface units.

These two partitions are then imported and re-exported from the module interface unit, greetings.ixx as follows:

export module greetings;
 
export import :func;
export import :types;

The syntax for exporting a partition is export import :<partition-name> . Of course, apart from these directives, the module interface unit may contain any other exports.

The content of main.cpp does not change. However, we need to change the commands we use to build the code, as follows:

cl /std:c++latest /EHsc /experimental:module /MD /c greetings-func.ixx
cl /std:c++latest /EHsc /experimental:module /MD /c greetings-types.ixx
cl /std:c++latest /EHsc /experimental:module /MD /c greetings.ixx
cl /std:c++latest /EHsc /experimental:module /MD main.cpp greetings-func.obj greetings-types.obj

Building this way is possible because we leveraged a naming scheme supported by the VC++ compiler for module partition units. That is <module-name>-<partition-name>.ixx . If you do not follow this scheme, then you need to use the /module:reference switch to specify the module partition interfaces.

Internal partitions

A partition does not have to be an interface unit. It could contain code that is not supposed to be exported from the module. Such a partition is called an internal partition and must be put in a file with the extension .cpp .

To see how these work, let’s modify the previous example where we used the rand() function in the greeter class. We will remove the details of generating a new integer to another function called next_rand() available in an internal partition called greetings:details . This function is not exported from the greetings module. The content of greetings-details.cpp is shown in the following snippet:

module;
 
#include <cstdlib>
 
module greetings:details;
 
int next_rand()
{
    return rand();
}

We need to modify the code in the greetings:types partition as follows (notice the import :details directive):

export module greetings:types;
 
import std.core;
import :details;
 
export struct greeter
{
   constexpr static const char* hellos[] {"Hello", "Hi", "Hey"};
   std::string operator()()
   {
      return hellos[next_rand() % 3] + std::string{", World!"};
   }
};

Nothing else needs to change except for the build commands. We have a new file to build, greetings-details.cpp and it requires a new compiler switch, /module:internalPartition to indicate that the file that is compiled is an internal partion of a module.

cl /std:c++latest /EHsc /experimental:module /MD /c greetings-func.ixx
cl /std:c++latest /EHsc /experimental:module /module:internalPartition /MD /c greetings-details.cpp
cl /std:c++latest /EHsc /experimental:module /MD /c greetings-types.ixx
cl /std:c++latest /EHsc /experimental:module /MD /c greetings.ixx
cl /std:c++latest /EHsc /experimental:module /MD main.cpp greetings-func.obj greetings-types.obj greetings-details.obj

Now, we can change the implementation details of the next_rand() function without affecting the module interface.

module greetings:details;
 
import std.core;
 
int next_rand()
{
    static std::random_device rd{};
    static std::mt19937 eng{rd()};
    static std::uniform_int_distribution<> uid {0, 1000};
    return uid(eng);
}

To build the program we only need to run the following commands:

cl /std:c++latest /EHsc /experimental:module /module:internalPartition /MD /c greetings-details.cpp
cl /std:c++latest /EHsc /experimental:module /MD main.cpp greetings-func.obj greetings-types.obj greetings-details.obj

Importing header units

What if the get_greeting_text() was already available in a header file, which perhaps you cannot modularize, maybe because you do not own the code? Modules support the importing of a special translation unit called header unit .

Suppose the header, called greetings.h looks like this:

#pragma once
 
inline const char* get_greeting_text()
{
    return "Hello, World!";
}

We can import this using the same import directive, as shown in the below snippet:

import std.core;
import "greetings.h";
 
int main()
{   
    std::cout << get_greeting_text() << '\n';
}

To build the program, this time, the build commands must be the following:

cl /std:c++latest /EHsc /experimental:module /MD /module:exportHeader greetings.h /Fogreetings.h.obj
cl /std:c++latest /EHsc /experimental:module /MD /module:reference greetings.h:greetings.h.ifc main.cpp greetings.h.obj

There are several compiler switches used here:

  • /module:exportHeader specifies that a header will be exported as a header unit. It requires the path to the header.
  • /Fo that specifies the name of a object file. Without this, the compiler only generates and .ifc file.
  • /module:reference that has an argument of the form <path-to-header>:<path-to-ifc> .

The .ifc file is a binary file generated by the compiler when exporting a module interface. It contains metadata about the module interface and is modeled based on the Internal Program Representation (IPR) for C++, developed by Gabriel Dos Reis and Bjarne Stroustrup. The IFC is the binary module interface (BMI), which is the term found in documentation.

Exporting templates

Templates can also be exported from a module. Let’s look at an example. The following module is available in a file called foo.ixx :

export module foo;
 
export template <typename T>
struct foo
{
    T value;
    
    foo(T const v):value(v){}
};
 
export template <typename T>
foo<T> make_foo(T const value)
{
    return foo(value);
}

In this snippet, the module foo contains a class template also called foo and function template called make_foo() that creates an instance of foo . Notice that the keyword export is preceding the keyword template . This module can be imported and its exports used in main.cpp as follows:

import std.core;
import foo;
 
int main()
{   
    auto fi = make_foo(42);
    std::cout << fi.value << '\n';
    
    auto fs = make_foo(std::string("modules"));
    std::cout << fs.value << '\n';
}

To build this program you must use the following build commands:

cl /std:c++latest /EHsc /experimental:module /MD /c foo.ixx
cl /std:c++latest /EHsc /experimental:module /MD main.cpp foo.obj

If you run this, it will print 42 and modules to the console.

See also

To learn more about modules in Visual C++ you can read the following:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Twisted Network Programming Essentials

Twisted Network Programming Essentials

Abe Fettig / O'Reilly Media, Inc. / 2005-10-20 / USD 29.95

Developing With Python's Event-driven Framework一起来看看 《Twisted Network Programming Essentials》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具