I struggled with my question title... I hope it will be clearer with a snippet.
This is derived and simplified from Attorney-Client idiom.
File header_A.h
// forward declaration
struct B;
template <typename T>
class Tag {
friend T;
// tag can be constructed only by a T
Tag() = default;
};
struct A {
int val = 0;
using tag = Tag<B>; // B is an incomplete type here
// using type in a definition though
// only a B object can use this function because only a B object can build a tag
void Set(int i, const tag &) { val = i; }
};
File header_B.h
struct B {
void Set(A& a, int i) { a.Set(i, Tag<B>{}); }
};
File main.cpp
#include "header_A.h"
#include "header_B.h"
int main() {
A a;
B b;
b.Set(a,42);
}
All this compiles and links just fine: LIVE
But, is it actually valid? What are the standard rules that apply?
In particular, why can I write using tag = Tag<B>;
and then use tag
in a definition, while B
is still unknown?
I suspect that it's because the actual type of B
is never needed (as hinted there), but I'd like to understand what happens exactly.
I struggled with my question title... I hope it will be clearer with a snippet.
This is derived and simplified from Attorney-Client idiom.
File header_A.h
// forward declaration
struct B;
template <typename T>
class Tag {
friend T;
// tag can be constructed only by a T
Tag() = default;
};
struct A {
int val = 0;
using tag = Tag<B>; // B is an incomplete type here
// using type in a definition though
// only a B object can use this function because only a B object can build a tag
void Set(int i, const tag &) { val = i; }
};
File header_B.h
struct B {
void Set(A& a, int i) { a.Set(i, Tag<B>{}); }
};
File main.cpp
#include "header_A.h"
#include "header_B.h"
int main() {
A a;
B b;
b.Set(a,42);
}
All this compiles and links just fine: LIVE
But, is it actually valid? What are the standard rules that apply?
In particular, why can I write using tag = Tag<B>;
and then use tag
in a definition, while B
is still unknown?
I suspect that it's because the actual type of B
is never needed (as hinted there), but I'd like to understand what happens exactly.
- I suspect it's also because it's a const reference, therefore no knowledge is needed about how a hypothetical instance would be copied/moved etc. – SoronelHaetir Commented Mar 21 at 21:32
- It is the passkey idiom – Jarod42 Commented Mar 22 at 13:14
1 Answer
Reset to default 1using tag = Tag<B>;
only requires a declaration for Tag
and a declaration for B
. Neither of the two have to be defined. Note that the identity of Tag<B>
as a type does only depend on declarations, not on the definitions of Tag
and B
. Tag<B>
is the same type, no matter how Tag
and B
are defined.
A definition of a class or is only required in special situations, e.g. when attempting to access a member or when doing overload resolution that depends on the contents of the class definition, etc. Basically whenever the contents of the class definition are necessary to decide the validity or behavior of a given code.
Similarly implicit instantiation of a class template specialization only happens when it is used in such a context.