1

This is excerpt from google's c++ coding guidelines.

How can we use a class Foo in a header file without access to its definition?

  • We can declare data members of type Foo* or Foo&.
  • We can declare (but not define) functions with arguments, and/or return values, of type Foo. (One exception is if an argument Foo or const Foo& has a non-explicit, one-argument constructor, in which case we need the full definition to support automatic type conversion.)
  • We can declare static data members of type Foo. This is because static data members are defined outside the class definition.

What I'm curious about is exception in the second bullet. Why is this so? Why is the full definition needed if we want to support automatic type conversion?

My guess is that compiler needs the full definition of the destination type because of the temporary object that is created in the implicit conversion. Am I guessing correctly? Is there more to it?

EDIT:

As I see it, the exception in the guideline is addressed to situation like this:

class A
{
    public:
        A( int );
};

class B
{
    public:
        B( A const &a );
};

int main()
{
    B b(2);
}

Here we have only one user-defined implicit conversion (from int to A), and call to constructor that accepts A const &. Only thing that makes sense in this exception is to support direct conversion from e.g. int to A, and then to B via constructor that accepts A const &, allowing client code to use this conversion chain without need to explicitly include header file where A class is declared.

LavaScornedOven
  • 737
  • 1
  • 11
  • 23
  • 1
    I'd say there's less to it - even before you worry about the temporary object, the compiler needs to know that the constructor exists before it can permit an implicit conversion. But yes, having decided that it's allowed, the compiler also has to emit the code to assign space for the temporary/argument and call the constructor, so it does need the size and the constructor declaration. – Steve Jessop Dec 16 '10 at 16:54
  • Aren't a constructor's declaration and source types declaration enough for compiler to know that it can convert? Thanks for the reply though. – LavaScornedOven Dec 16 '10 at 17:06
  • @Vedran: yes, that's what Steve said. and for the google guideline it means the guideline's rule exception is meaningless. the guideline talks about "without access to [the class] definition". in that case neither size nor constructor definition is known. so it's a non-issue. – Cheers and hth. - Alf Dec 16 '10 at 19:02
  • Google's coding guidelines also forbid exceptions. I think I'll skip. – Puppy Dec 16 '10 at 20:01
  • 1
    @DeadMG The provided explanation for that. Although it would be beneficial to use exceptions from their viewpoint, transition to exceptions would be too costly. – LavaScornedOven Dec 16 '10 at 21:03
  • 1
    @Vedran: the constructor *declaration* appears in the class *definition*. Hence if `Foo` has that constructor, then the class definition for `Foo` is required to know about the implicit conversion. The constructor definition isn't needed, that can be linked later. – Steve Jessop Dec 16 '10 at 22:00
  • @Steve +1 Yes, I think I understood your first comment fully just now. So what is your viewpoint on this exception to the rule? Isn't it beating purpose of all the measure Google made to reduce header file dependency? Of course, this observation can be true only if my reasoning of why they would even introduce such an exception is correct. – LavaScornedOven Dec 16 '10 at 23:12
  • @Vedran: I still don't understand what the difference is that Google is talking about. In your code, if the author of B wants users to be able to construct an instance of B from an `int`, then they should have provided an `int` constructor, not a `const A&` constructor. Then there would be no need for the definition of A to be visible from `main`. So it looks to me like, "if you don't quite manage to keep A out of it, then the caller needs the definition of A", and I'm puzzled why Google would think that needs special mention. – Steve Jessop Dec 17 '10 at 12:03

3 Answers3

2

The C++ language doesn't differentiate between code in header files and other file. It does not even require that a header is a file. So purely technically the question is meaningless, but in practice you restrict what you do in header files so as not to run afoul of the One Definition Rule. Without restricting yourself, users would have to be careful to only include the header file in one translation unit. With proper restrictions, the header file can be freely included in multiple translation units.

An incomplete type is one where the size is not known, where sizeof cannot be used.

When the class definition is not known, class Foo is necessarily incomplete.

This means you cannot do things that requires the size to be known. And since incompleteness means that members are not known (they would necessarily be known if the size was known) you can't generally call any members. Exception: you can call the destructor, like in delete pFoo, and the compiler must accept that, but it's Undefined Behavior if class Foo has a non-trivial destructor.

The exception noted in the Google guidelines is, however, meaningless.

EDIT: I discovered that people on SO like it better when things are spelled out in detail, so, adding discussion of why the guideline is meaningless.

The guideline says you can "declare (but not define)" but that "one exception is if an argument Foo or const Foo& has a non-explicit, one-argument constructor".

The declaration does not have anything to do with constructors, which one can affirm by simply trying it out:

#include <iostream>

struct Foo;

Foo bar( Foo const& );  // Declaration of function bar, works fine.

struct Foo
{
    int x_;
    Foo( int x ): x_( x ) {}       // Converting constructor.
};

int main()
{
    std::cout << bar( 42 ).x_ << std::endl;
}

Foo bar( Foo const& foo ) { return foo; }

In conclusion, again, the Google guidelines' exception is meaningless.

Cheers & hth.,

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 1
    I'm puzzled too what that parenthetical exception is on about. As far as I can make sense of it, it appears to mean that in the case where there's an implicit conversion, one *can't* "declare (but not define) functions with arguments and return values of type Foo". But that's false, and obviously so, so I rule it out as what they meant. I guess they're talking about some specific use case that they don't describe but also don't realise is unknown to the reader. Of course in their "exceptional" case, like all other cases, it's the calling code that needs the class definition. – Steve Jessop Dec 16 '10 at 21:59
0

Suppose that foo.h knows about Foo declaration only

//foo.h

class Foo;
void f(const Foo &); // It is possible to use the reference.

Full definition is in foo.cpp

// foo.cpp

class CanBeConvertedToFoo;
class Foo
{
   Foo (const CanBeConvertedToFoo & x); // implicit constructor
}

class CanBeConvertedToFoo is implicit convertable to Foo; But it is unknown in some.cpp.

// some.cpp

#include "foo.h"
void g(const CanBeConvertedToFoo & x) {
   f(x); // Is it known about implicit conversion ?
}
Alexey Malistov
  • 26,407
  • 13
  • 68
  • 88
  • That means that the definition of `g` needs the definition of `CanBeConvertedToFoo`, but the definition of `Foo` doesn't. – Philipp Dec 16 '10 at 16:57
  • +1 This interpretation is on the name-lookup level, which is connected to Steve's comments. Can you find motivation to introduce such an exception to the forementioned rule? – LavaScornedOven Dec 16 '10 at 23:37
  • -1 In this case one can certainly "declare (but not define)" function `g`. It is not the case that `g` is "an exception" to that as the Google guidelines claim. So this does not answer what's meant by "an exception": it only illustrates one way that the Google claim is meaningless. – Cheers and hth. - Alf Feb 04 '14 at 10:24
0

I don't know whether the exception in the second point is true. Implicit conversions must be know only when a function is called, not when it is declared, so the following works even though C is incomplete while f is declared:

#include <iostream>
class C;
void f(C);
struct C { C(int i) { std::cout << "C(" << i << ")" << std::endl; } };
void f(C c) { std::cout << "f(C)" << std::endl; }
int main() { f(2); }
Philipp
  • 48,066
  • 12
  • 84
  • 109