C++ class inheritance, Visual Studio and modules

It looks like I found a bug in Visual Studio how it processes module code.

Here's some non-module code that compiles and runs fine, a couple of class header files and an test implementation source file:

Box.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef BOX_HPP
#define BOX_HPP

#include <iostream>

class Box
{
public:
   Box(double l, double w, double h) : m_length { l }, m_width { w }, m_height { h }
   { std::cout << "Box(double, double, double) called.\n"; }

   explicit Box(double side) : Box { side, side, side }
   { std::cout << "Box(double) called.\n"; }

   Box() { std::cout << "Box() called.\n"; }

   double volume() const { return m_length * m_width * m_height; }

   double getLength() const { return m_length; }
   double getWidth()  const { return m_width; }
   double getHeight() const { return m_height; }

private:
   double m_length { 1.0 }; 
   double m_width  { 1.0 };
   double m_height { 1.0 };
};

#endif 

Carton.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef CARTON_HPP
#define CARTON_HPP

#include <string>
#include <string_view>

#include "Box.hpp"

class Carton : public Box
{
   using Box::Box;  // inherit Box class constructors 

public:
   Carton(double length, double width, double height, std::string_view mat)
      : Box { length, width, height }, m_material { mat }
   { std::cout << "Carton(double,double,double,string_view) called.\n"; }

private:
   std::string m_material { "Cardboard" };
};

#endif 

main_no_modules.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

#include "Carton.hpp"

int main()
{
   Carton cart;                                        // calls inherited default constructor
   std::cout << '\n';

   Carton cube { 4.0 };                                // calls inherited constructor
   std::cout << '\n';

   Carton copy { cube };                               // calls default copy constructor

   Carton carton { 1.0, 2.0, 3.0 };                    // calls inherited constructor
   std::cout << '\n';

   Carton cerealCarton(50.0, 30.0, 20.0, "Chipboard"); // calls Carton class constructor
}

While inheriting base class constructor(s) in a derived class is something I haven't seen before I understand what it is doing.

This code compiles and has the following output:
Box() called.

Box(double, double, double) called.
Box(double) called.

Box(double, double, double) called.

Box(double, double, double) called.
Carton(double,double,double,string_view) called.

"Modularizing" this code isn't all that hard. Remove the header guards in the headers, change the file extension from hpp to cppm, add export name; add the export keyword to the class definitions and change the #includes to import statements. Change the #includes in the test implementation file to imports.

Box.cppm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export module box;

import <iostream>;

export class Box
{
public:
   Box(double l, double w, double h) : m_length { l }, m_width { w }, m_height { h }
   { std::cout << "Box(double, double, double) called.\n"; }

   explicit Box(double side) : Box { side, side, side }
   { std::cout << "Box(double) called.\n"; }

   Box() { std::cout << "Box() called.\n"; }

   double volume() const { return m_length * m_width * m_height; }

   double getLength() const { return m_length; }
   double getWidth()  const { return m_width; }
   double getHeight() const { return m_height; }

private:
   double m_length { 1.0 };
   double m_width  { 1.0 };
   double m_height { 1.0 };
};

Carton.cppm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export module carton;

import <string>;
import <string_view>;
import <iostream>;

import box;

export class Carton : public Box
{
   using Box::Box;  // inherit box class constructors 

public:
   Carton(double length, double width, double height, std::string_view mat)
      : Box { length, width, height }, m_material { mat }
   { std::cout << "Carton(double,double,double,string_view) called.\n"; }

private:
   std::string m_material { "Cardboard" };
};

main_modules.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import <iostream>;

import carton;

int main()
{
   Carton cart;                                        // calls inherited default constructor
   std::cout << '\n';

   Carton cube { 4.0 };                                // calls inherited constructor
   std::cout << '\n';

   Carton copy { cube };                               // calls default copy constructor

   Carton carton { 1.0, 2.0, 3.0 };                    // calls inherited constructor
   std::cout << '\n';

   Carton cerealCarton(50.0, 30.0, 20.0, "Chipboard"); // calls Carton class constructor
}

So far nothing really difficult to understand, converting existing non-module code to modules is not that hard. And I haven't had any problem with other module code I've mucked around with until now.

Compile the above and VS vomits up an error:
Error	C2512	'Carton': no appropriate default constructor available

Comment out lines 7 & 8 and the error goes away.

Huh? Somehow non-modular code that compiles fine gives VS heartburn when done as modules. Is this a bug in VS or a possible bug in the over-all C++ implementation of modules?

I wish I could test this in a different compiler, but so far MSVC is the only compiler I know of that has module support. All the others have partial to no support for modules. ::sad::
Last edited on
To me it looks like a bug in the non-module version. The code should indeed not compile, since by defining a constructor for Carton that takes parameters you're deleting the default constructor generated by the compiler. To put the default constructor back in you would need to add Carton() = default;. It would seem the fact that Carton is derived and Box does have an explicitly defined default constructor is confusing MSVC, but only when #including carton.hpp in main_no_modules.cpp.
I just tested the non-module version in Code::Blocks, using TDM-GCC (what I hope is the latest version) as my C++ compiler, and the code compiles fine.

I've tried in the recent past to compile module code in C::B, as well as MSYS2, and failed. MSVC is the only compiler I have that work with modules.

So I stand behind my earlier statement/question, there is an apparent bug in MSVC regarding modules.

Using modules does have some subtle differences vs. headers I am slowly discovering. imports are not #includes, a header included in another header doesn't need to be included in an implementation file, but an import might need to be duplicated when using modules.

for instance: import <random>; in an interface file imported into an implementation file can require the implementation file to also import <random>; as well.

The code I am using is from:
Beginning C++20: From Novice to Professional
https://www.amazon.com/gp/product/1484258835/
In the non-modular version in carton.hpp if you comment out line 11 (using), do you then get the same error?
Non-modular version comment out the using statement and I get the same error as with the module code, plus a couple of other errors about not being able to convert an initializer list to Carton.

I even tried to import box into the main_modules.cpp implementation file earlier (before all the commenting) and still had the error I received that started this topic.

Weird, and weirder.

It seems modules need a bit of tweaking from non-module code. Add Carton() = default; to the Carton class def and the damned thing compiles without a hitch.

Add the default ctor and comment out the using statement in the module code and I get the same two errors about not being able to convert an initializer list.

M'ok, module interface files are not header files. I can't repeat that too much.

So maybe it isn't MSVC causing the problem, it is me and my admittedly imperfect understanding of modular vs. non-modular code. :)

All I can say is I was slavishly following what the authors wrote. Mea Culpa and all that jazz.

I'm learning the hard way, banging my head against a hard surface repeatedly trying to resolve code issues is sooooooo much fun.
Last edited on
Add the default ctor and comment out the using statement in the module code and I get the same two errors about not being able to convert an initializer list.


That would be as expected.

You could always report it as a 'bug' with MS and see what they say. I've reported several in the past - a couple have been my fault, several have been fixed and a couple are still outstanding as the fixes requires a ABI change. Ah... You need a MS account though.

Topic archived. No new replies allowed.