Class member visibility in member function declaration signature - c++

Why does this work:
template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};
But this does not (a and f swapped places):
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
A a;
};
saying that a is not declared in that scope (inside decltype) but adding explicit this-> makes it work.

template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};
This works because within a trailing return type, members of the surrounding class are visible. Not all members, but only the members that are declared prior to it (in a trailing return type, the class is not considered to be complete, as opposed to function bodies). So what is done here:
As we are in a template, a lookup is done to see whether a is dependent or not. Since a was declared prior to f, a is found to refer to a class member.
By the template rules in C++, it is found that a refers to a member of the current instantiation since it is a member of instantiations of the surrounding template. In C++, this notion is used mainly to decide whether names are dependent: If a name is known to refer to the surrounding template's members, it is not necessarily needed to be looked up when instantiating, because the compiler already knows the code of the template (which is used as the basis of the class type instantiated from it!). Consider:
template<typename T>
struct A {
typedef int type;
void f() {
type x;
A<T>::type y;
}
};
In C++03, the second line declaring y would be an error, because A<T>::type was a dependent name and needed a typename in front of it. Only the first line was accepted. In C++11, this inconsistency was fixed and both type names are non-dependent and won't need a typename. If you change the typedef to typedef T type; then both declarations, x and y will use a dependent type, but neither will need a typename, because you still name a member of the current instantiation and the compiler knows that you name a type.
So a is a member of the current instantiation. But it is dependent, because the type used to declare it (A) is dependent. However this doesn't matter in your code. Whether dependent or not, a is found and the code valid.
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
A a;
};
In this code, again a is looked up to see whether it is dependent and/or whether it is a member of the current instantiation. But since we learned above that members declared after the trailing return type are not visible, we fail to find a declaration for a. In C++, besides the notion "member of the current instantiation", there is another notion:
member of an unknown specialization. This notion is used to refer to the case where a name might instead refer to a member of a class that depends on template parameters. If we had accessed B::a, then the a would be a member of an unknown specialization because it is unknown what declarations will be visible when B is substituted at instantiation.
neither a member of the current, nor a member of an unknown specialization. This is the case for all other names. Your case fits here, because it is known that a can never be a member of any instantiation when instantiation happens (remember that name lookup cannot find a, since it is declared after f).
Since a is not made dependent by any rule, the lookup that did not find any declaration is binding, meaning there is no other lookup at instantiation that could find a declaration. Non-dependent names are lookup up at template definition time. Now GCC rightfully gives you an error (but note that as always, an ill-formed template is not required to be diagnosed immediately).
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(this->a.f(b))
{
}
A a;
};
In this case, you added this and GCC accepted. The name a that follows this-> again is lookup at to see whether it might be a member of the current instantiation. But again because of the member visibility in trailing return types, no declaration is found. Hence the name is deemed not to be a member of the current instantiation. Since there is no way that at instantiation, S could have additional members that a could match (there are no base classes of S that depend on template parameters), the name is also not a member of an unknown specialization.
Again C++ has no rules to make this->a dependent. However it uses this->, so the name must refer to some member of S when it is instantiated! So the C++ Standard says
Similarly, if the id-expression in a class member access expression for which the type of the object expression is the current instantiation does not refer to a member of the current instantiation or a member of an unknown specialization, the program is ill-formed even if the template containing the member access expression is not instantiated; no diagnostic required.
Again no diagnostic is required for this code (and GCC actually doesn't give it). The id-expression a in the member access expression this->a was dependent in C++03 because the rules in that Standard were not as elaborated and fine-tuned as in C++11. For a moment let's imagine C++03 had decltype and trailing return types. What would this mean?
The lookup would have been delayed until instantiation, because this->a would be dependent
The lookup at instantiation of, say, S<SomeClass> would fail, because this->a would not be found at instantiation time (as we said, trailing return types do not see members declared later).
Hence, the early rejection of that code by C++11 is good and useful.

The body of a member function is compiled as if it was defined after the class. Therefore everything declared in the class is in scope at that point.
However, the declaration of the function is still inside the class declaration and can only see names that precede it.
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b)); // error - a is not visible here
A a;
};
template <typename A>
template <typename B>
auto S<A>::f(B b) ->
decltype(a.f(b))
{
return a.f(b); // a is visible here
}

The Standard says (section 14.6.2.1):
If, for a given set of template arguments, a specialization of a template is instantiated that refers to a member of the current instantiation with a qualified-id or class member access expression, the name in the qualified-id or class member access expression is looked up in the template instantiation context.
this->a is a class-member access expression, therefore this rule applies and lookup takes place at the point of instantiation, where S<A> is complete.
Finally, this doesn't solve your problem at all, because section 5.1.1 says:
If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type “pointer to cv-qualifier-seq X” between the optional cv-qualifier-seq and the end of the function-definition, member-declarator, or declarator. It shall not appear before the optional cv-qualifier-seq
and it shall not appear within the declaration of a static member function (although its type and value category are defined within a static member function as they are within a non-static member function).
So you can't use this-> here, since it is before the cv-qualifier-seq part of the function declaration.
Wait, no it isn't! Section 8.4.1 says
The declarator in a function-definition shall have the form
D1 ( parameter-declaration-clause) cv-qualifier-seq opt ref-qualifier opt exception-specification opt attribute-specifier-seq opt trailing-return-type opt

Related

Is a local class dependent if declared within a function template?

Current C++ compilers (latest gcc, clang) require the typename keyword in the example below:
template<class T>
struct A
{
};
template<class T>
void f(T)
{
struct C
{
};
typedef typename A<C>::Type Type; // typename required
}
If typename is omitted gcc (4.9, 5.0) reports the error:
need 'typename' before 'A<f(T)::C>::Type' because 'A<f(T)::C>' is a dependent scope
This example is otherwise well-formed according to my reading of the C++11 standard.
This behaviour seems to be covered by the following wording:
[temp.dep.type]/8
A type is dependent if it is
a template parameter,
a member of an unknown specialization,
a nested class or enumeration that is a member of the current instantiation,
a cv-qualified type where the cv-unqualified type is dependent,
a compound type constructed from any dependent type,
an array type constructed from any dependent type or whose size is specified by a constant expression
that is value-dependent,
a simple-template-id in which either the template name is a template parameter or any of the template
arguments is a dependent type or an expression that is type-dependent or value-dependent, or
denoted by decltype(expression), where expression is type-dependent.
However, according to [class.local] the class C is a local class rather than a nested class. If so, why should A<C> be treated as dependent?
EDIT
For bonus points, if the example is modified by adding a member enum to C as follows:
template<typename T>
struct A
{
typedef T Type;
};
template<class T>
void f(T)
{
struct C
{
enum { value = T::value };
};
typedef typename A<C>::Type Type; // typename required
}
Should A<C> now be treated as dependent?
According to my understanding (and the current wording of the standard), C in your example is not dependent. Neither is A<C>::Type, so the typename is not required.
There is a fundamental difference between nested classes of class templates and local classes in function templates: The latter cannot be specialized, thus any reference to a local class inside a function template is uniform. That is, in every specialization of f, C refers to the class C that is defined in this function template f. That is not the case with class templates as you can indeed explicitly specialize members on their own (as covered in [temp.expl.spec]
/(1.6)):
template <typename T>
class A { class C{}; };
template <>
class A<int>::C { int i; };
However:
A type is dependent if it is
a compound type constructed from any dependent type,
So if the definition was done as in dyp's example, C would be dependent as it is constructed from T.
There are unclarities in the standards wording that are being discussed in the comment section, e.g. about definitions of member functions that depend on T and how that transposes to the classes dependency.
Following is my reasoning, hope it helps. The local C does not instantiate until f instantiate. So, A<C> is not an instantiation and is opaque to the compiler when it sees it. Because of the opaqueness, the compiler cannot determine whether A<C>::Type is a nested type name or a data member or a method. However, by default, the compiler does not see A<C>::Type as a nested type name. Therefore, an explicit specification is needed.
There doesn't appear to be anything in the standard to state that the typename keyword should be necessary here. The wording does not explicitly state otherwise, either, which may have led GCC to take a bit of a shortcut in treating f<T>(T)::C (being a local class in a function template specialisation) as dependent on T — by extension, this would make A<[f<T>(T)::]C>::Type dependent.
Core defect 1484 wasn't raised for this issue specifically, but I think the additional non-normative text it proposes makes the intention clear and, were it in a standard, GCC would compliantly not require the typename keyword here.

Is a function type dependent if it depends only on its own template parameters?

I came across an inconsistency in the way current C++ compilers (clang/gcc) determine whether a name is dependent. In the following example, A::f is dependent but ::f is not, resulting in an error when the latter is used.
template<typename>
struct B
{
typedef int Type;
};
template<typename U>
static U f(U u);
template<typename T>
struct A
{
template<typename U>
static U f(U u);
typename B<decltype(f(0))>::Type m1; // typename required
B<decltype(::f(0))>::Type m2; // typename not required
};
The inconsistent part is that the declaration of A::f does not depend on a template parameter of A, meaning it seems unnecessary to treat it as a dependent name.
This behaviour seems to be covered by the following wording in the C++11 standard:
[temp.dep.expr]/3
An id-expression is type-dependent if it contains
an identifier associated by name lookup with one or more declarations declared with a dependent type
[temp.dep.type]/3
A type is dependent if it is
a compound type constructed from any dependent type
The declaration of ::f is clearly not dependent, as its type depends only on its own template parameters. Why should A::f be treated differently?
I think that based on the standard, f is non-dependent, actually.
14.6.2.2 Type-dependent expressions [temp.dep.expr]
3 An id-expression is type-dependent if it contains
an identifier associated by name lookup with one or more declarations declared with a dependent type,
This applies equally to the global template function as it does to the member template function: not at all. The return type of U is dependent inside the definitions of the template functions, but for the caller, the function type of f<int> has already been transformed from U(U) to int(int). At any rate, it wouldn't explain why compilers treat the two cases differently, and it also would not explain why a non-template member function is also treated as dependent.
a template-id that is dependent,
a conversion-function-id that specifies a dependent type, or
These do not apply. There is no < or > that must always be present in a template-id, and there is no conversion function being called.
a nested-name-specifier or a qualified-id that names a member of an unknown specialization;
See below.
or if it names a static data member of the current instantiation that has type "array of unknown bound of T" for some T (14.5.1.3).
This also does not apply: there are no arrays involved.
So it depends on whether f is a member of an unknown specialization. But it isn't:
14.6.2.1 Dependent types [temp.dep.type]
5 A name is a member of an unknown specialization if it is
A qualified-id in which [...].
A qualified-id in which [...].
An id-expression denoting the member in a class member access expression (5.2.5) in which [...].
These cannot apply: f is neither qualified nor part of a class member access expression.
Since the only way f can be dependent is if it is a member of an unknown specialization, and it is not a member of an unknown specialization, f must not be dependent.
As for why compilers nonetheless treat it as dependent, I have no answer. Either some part of my answer here is wrong, the compilers have bugs, or the compilers follow a different version of the C++ standard. Testing with an example that works regardless of whether names are dependent shows a few variations in compiler treatment:
#include <cstdio>
void f(const char *s, ...) { std::printf("%s: non-dependent\n", s); }
struct S1 { };
template <typename T>
struct S2 {
static S1 a;
static S1 b() { return {}; }
template <typename U>
static U c() { return {}; }
static void z() {
f("S1()", S1()); // sanity check: clearly non-dependent
f("T()", T()); // sanity check: clearly dependent
f("a", a); // compiler agreement: non-dependent
f("b()", b()); // compiler disagreement: dependent according to GCC 4.8, non-dependent according to clang
f("c<T>()", c<T>()); // sanity check: clearly dependent
f("c<S1>()", c<S1>()); // compiler agreement: dependent
f("decltype(b())()", decltype(b())()); // compiler agreement: dependent
}
};
void f(const char *s, S1) { std::printf("%s: dependent\n", s); }
// Just to show it's possible to specialize the members
// without specializing the full template.
template <>
S1 S2<S1>::b() { return {}; }
template <>
template <>
S1 S2<S1>::c<S1>() { return {}; }
int main() {
S2<S1>::z();
}
This difference in clang's treatment of b(), decltype(b())(), and c<S1>() is particularly troubling to me. It just doesn't make any sense. They're clearly all equally dependent. I can understand from an implementation point-of-view that care must be taken not to generate code for the member functions just yet because there might be specialisations of S2<S1>::b or S2<S1>::c<S1>, but that applies to all, and has no effect on the return type.

gcc and clang implicitly instantiate template arguments during operator overload resolution

Consider this code:
struct A; // incomplete type
template<class T>
struct D { T d; };
template <class T>
struct B { int * p = nullptr; };
int main() {
B<D<A>> u, v;
u = v; // doesn't compile; complain that D<A>::d has incomplete type
u.operator=(v); // compiles
}
Demo. Since u.operator=(v) compiles but u = v; doesn't, somewhere during the overload resolution for the latter expression the compiler must have implicitly instantiated D<A> - but I don't see why that instantiation is required.
To make things more interesting, this code compiles:
struct A; // incomplete type
template<class T>
struct D; // undefined
template <class T>
struct B { int * p = nullptr; };
int main() {
B<D<A>> u, v;
u = v;
u.operator=(v);
}
Demo.
What's going on here? Why does u = v; cause the implicit instantiation of D<A> - a type that's nowhere used in the body of B's definition - in the first case but not the second?
The entire point of the matter is ADL kicking in:
N3797 - [basic.lookup.argdep]
When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered
during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope
friend function or function template declarations (11.3) not otherwise visible may be found.
following:
For each argument type T in the function call, there is a set of zero or more associated namespaces and a
set of zero or more associated classes to be considered. [...] The sets of
namespaces and classes are determined in the following way:
If T is a class type [..] its associated classes are: ...
furthemore if T is a class template specialization its associated namespaces and classes also include: the namespaces and classes associated with the
types of the template arguments provided for template type parameters
D<A> is an associated class and therefore in the list waiting for its turn.
Now for the interesting part [temp.inst]/1
Unless a class template specialization has been explicitly instantiated (14.7.2) or explicitly specialized (14.7.3),
the class template specialization is implicitly instantiated [...] when the completeness of the class type affects the semantics of the program
One could think that the completeness of the type D<A> doesn't affect at all the semantic of that program, however [basic.lookup.argdep]/4 says:
When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2)
except that:
[...]
Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective
namespaces even if they are not visible during an ordinary lookup (11.3)
i.e. the completeness of the class type actually affects friends declarations -> the completeness of the class type therefore affects the semantics of the program.
That is also the reason why your second sample works.
TL;DR D<A> is instantiated.
The last interesting point regards why ADL starts in the first place for
u = v; // Triggers ADL
u.operator=(v); // Doesn't trigger ADL
§13.3.1.2/2 dictates that there can be no non-member operator= (other than the built-in ones). Join this to [over.match.oper]/2:
The set of non-member candidates is the result of the unqualified lookup of operator# in the context
of the expression according to the usual rules for name lookup in unqualified function calls (3.4.2)
except that all member functions are ignored.
and the logical conclusion is: there's no point in performing the ADL lookup if there's no non-member form in table 11. However [temp.inst]p7 relaxes this:
If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.
and that's the reason why clang triggered the entire ADL -> implicit instantiation process in the first place.
Starting from r218330 (at the time of writing this, it has been committed a few minutes ago) this behavior was changed not to perform ADL for operator= at all.
References
r218330
clang sources / Sema module
cfe-dev
N3797
Thanks to Richard Smith and David Blaikie for helping me figuring this out.
Well, I think in Visual Studio 2013 code should look like(without = nullptr):
struct A; // incomplete type
template<class T>
struct D { T d; };
template <class T>
struct B { int * p; };
int void_main() {
B<D<A>> u, v;
u = v; // compiles
u.operator=(v); // compiles
return 0;
}
In this case it should compile well just because incomplete types can be used for specific template class specialization usage.
As for the run-time error - the variable v used without initialization - it's correct - struct B does not have any constructor => B::p is not initialized and could contain garbage.

When instantiating a template, should members of its incomplete argument types be visible?

In the following example, A has a member typedef Instantiate which causes the instantiation of B<A>.
template<typename T>
struct B
{
typedef typename T::Before Before; // ok
typedef typename T::After After; // error: no type named 'After' in 'A<int>'
};
template<typename T>
struct A
{
typedef int Before;
typedef typename B<A>::After Instantiate;
typedef int After;
};
template struct A<int>; // instantiate A<int>
All the compilers I've tried report that, while A::Before is visible, A::After is not. Is this behaviour compliant with the standard? If so, where does the standard specify which names in A should be visible during instantiation of B<A>?
If dependent names are "looked up at the point of the template instantiation", what does this mean in the scenario of a name qualified by a template parameter such as T::After?
EDIT: Note that the same behaviour occurs when A is not a template:
template<typename T>
struct B
{
typedef typename T::Before Before; // ok
typedef typename T::After After; // error: no type named 'After' in 'A'
};
struct A
{
typedef int Before;
typedef B<A>::After Instantiate;
typedef int After;
};
.. and G++ accepts the following, but Clang does not:
template<typename T>
struct B
{
static const int value = 0;
static const int i = T::value; // clang error: not a constant expression
};
struct A
{
static const int value = B<A>::value;
};
EDIT: After some reading of the C++03 standard:
[temp.dep.type] A type is dependent if it is a template parameter
Therefore T is dependent.
[temp.res] When looking for the declaration of a name used in a template definition, the usual lookup rules are used for nondependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known.
The lookup of T::After is therefore postponed until the argument for T is known.
[temp.inst] Unless a class template specialization has been explicitly instantiated ... the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type.
Therefore the declaration of A<int>::Instantiate requires the instantiation of B<A> (because it is used in a nested-name-specifier.)
A<int>::After is not visible at the point of declaration of A<int>::Instantiate, so the behaviour of the compiler makes sense - but I haven't seen anything in C++03 that explicitly describes this behaviour. The closest thing was this somewhat vague paragraph:
[temp.dep.res]
In resolving dependent names, names from the following sources are considered:
— Declarations that are visible at the point of definition of the template.
Whether typename T::Before is valid is not explicitly said by the spec. It is subject of a defect report (because the Standard can very reasonably be read to forbid it): http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#287 .
Whether typename T::After is invalid can also very reasonably be read to be true by the spec, and actually it makes quite a bit of sense (and aforementioned DR still keeps it ill-formed). Because you have an instantiation of a class A<Foo>, which references another class A<Bar> during a period where a member Baz has not yet been declared, and that makes a reference back to A<Foo>::Bar. That is ill-formed in the case of non-templates aswell (try to "forget" for a moment that you are dealing with templates: surely the lookup of B<A>::After is done after the A template was completely parsed, but not after the specific instantiation of it was completely created. And it is the instantiation of it that actually will do the reference!).
struct A {
typedef int Foo;
typedef A::Foo Bar; // valid
typedef A::Baz Lulz; // *not* valid
typedef int Baz;
};
T::Before and T::After are dependent names due to [temp.dep.type]/8 and /5.
Dependent names are looked up "at the point of the template instantiation (14.6.4.1) in both the context of the template definition and the context of the point of instantiation." [temp.dep]/1
I interpret this as: They're looked up when the template is instantiated. Where are they looked up? At the context of the template definition and the context of the point of instantiation.
[temp.dep.type]/7 On the other hand states:
If, for a given set of template arguments, a specialization of a template is instantiated that refers to a member of the current instantiation with a qualified-id or class member access expression, the name in the qualified-id or class member access expression is looked up in the template instantiation context.
[temp.point]/7 Defines the instantiation context as follows:
The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.
Therefore, we need to know what the point of instantiation is.
[temp.point]/4
For a class template specialization [...], if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the
enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template.
Although the injected class name A is arguably a context that depends (as a layman term) on the template parameters of A, the name A itself is not a dependent name. Correction by Johannes Schaub: It is a dependent name. See [temp.local]/1 and [temp.dep.type]/8 => A is a dependent type.
Therefore, this condition is not fulfilled, and B<A> should be instantiated before A<int>.

Disambiguator template keyword for a template member of a template: when exactly?

A question regarding template disambiguator was given here:
template disambiguator
and in the answer we can read:
ISO C++03 14.2/4
When the name of a member template specialization appears after . or -> in a postfix-expression, or after nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
Now here comes my conrete example that I don't quite understand:
template <class T>
class Base {
public:
template <int v>
static int baseGet() {return v;}
class InnerA {
public:
template <int v>
static int aget() {return v;}
};
class InnerB {
public:
typedef Base BaseType;
typedef BaseType::InnerA OtherType;
template <int v>
static int baseGet() {return BaseType::baseGet<v>();} //(A)
template <int v>
static int aget() {return OtherType::aget<v>();} //(B)
};
};
It obviously fails to compile. You need template in the line (B): OtherType::template aget<v>();.
However, both g++ (4.4.3) and clang++ (2.9) don't complain about the lack of template in the line (A). Why? BaseType depends on the type T, does it not? Is it a small depart from the standard by those compilers, or do I misunderstand something in the standard?
They implement the C++0x specification, where Base is the current instantiation. And C++0x allows to omit template keyword in such a case. Since BaseType is a typedef for Base, when you say BaseType, that names the current instantiation too.
To quote the spec, since you seem to be interested in spec refs
A name is a member of the current instantiation if it is [...]
A qualified-id in which the nested-name-specifier refers to the current instantiation and that, when looked up, refers to at least one member of the current instantiation or a non-dependent base class thereof.
and
A name refers to the current instantiation if it is [...]
in the definition of a [...] nested class of a class template, [...], the injected-class-name (Clause 9) of the class template or nested class
and (the modified 14.2/4 that you quoted)
[...] or the nested-name-specifier in the qualified-id refers to a dependent type, but the name is not a member of the current instantiation (14.6.2.1), the member template name must be prefixed by the keyword template. [...]
Note: In C++03 your code is ill-formed because both BaseType and OtherType are dependent. The spec says:
A type is dependent if it is [...]
a template parameter
a qualified-id with a nested-name-specifier which contains a class-name that names a dependent type
a template-id in which either the template name is a template parameter or any of the template arguments is a dependent type
(note that Base is equivalent to Base<T>, which is the base on which Base and BaseType::InnerA are dependent types).
Note that "explicitly depends" in your quote is a pre-standard term, and was gotten rid of fairly lately (I believe it was at December1996). It basically meant (in this context) a qualified-id in which the qualifier is dependent or a class member access (a->x / a.x) where the a was dependent. After "explicitly depends" was removed from the draft, it was still lurking around at some places, and even C++0x has still references to "explicitly depends" in a note at 14.6.2p2:
the base class name B<T>, the type name T::A, the names B<T>::i and pb->j explicitly depend on the template-parameter.
Because OtherType is a nested dependent name while BaseType is not a nested type to begin with.
You need to use template for nested dependent types.
The keywords here are:
Dependent Type
Nested Type
If a type is both, then you've to use template.
OtherType is both dependent type (it depends on T) as well as nested type
BaseType is only dependent type (it depends on T). Its not a nested type.
BaseType does depend on the type T- but so does InnerB. In effect, from the perspective of any code inside BaseType<T>, it does not depend on T.