Function Instantiation: Where? Or is it?

Folks –

This has had me puzzled for a while, and I could use an answer asap.
I am implementing concepts in Clang and seem to be missing a crucial piece of understanding, either of C++, or of Clang.
Basically, I have gotten mostly everything else implemented and working fine to the extend that I want them to, except for concept members instantiation (?).

Basic Example: instantiation.cpp

Folks –

This has had me puzzled for a while, and I could use an answer asap.
I am implementing concepts in Clang and seem to be missing a crucial piece of understanding, either of C++, or of Clang.
Basically, I have gotten mostly everything else implemented and working fine to the extend that I want them to, except for concept members instantiation (?).

Basic Example: instantiation.cpp

concept B {
void f(int) { }
}

concept_map B {
void f(int) { }
}

template
requires (B)
void func(T a) {
// f(a);
f(0);
}

int main(int argc, char **argv)
{
int i=0;
func(i);
}

Compilation: clang++ instantiation.cpp -o example

It seems to be failing at link time, with the following error message:

Undefined symbols:
“__ZN1B1fEi”, referenced from:
_Z4funcIiEvT in cc-MXLAc6.o
ld: symbol(s) not found

Any idea? Anyone?

  1. At which point are functions marked for instantiation?

Look for calls to Sema::MarkDeclarationReferenced throughout Sema.

2 ) At which point are they instantiated ?

At the end of the translation unit.

  1. If instantiated, why would they not be visible by the linker?

Probably because the template instantiation mechanism couldn’t find the definition of B::f(int).

  • Doug

Thanks. That was a lot helpful. However, I do have another question. Consider the following two case scenari:

Case 1:

Thanks. That was a lot helpful. However, I do have another question. Consider the following two case scenari:

Case 1:

The previous one, with
(1) f() defined as a concept member, and
(2) func() a restricted template

Case 2:

(1) f() is a global function, and
(2) func() is a simple template, as in

void f() { }

template
void func(T a) {
f(0);
}

int main(int argc, char **argv)
{
int i=0;
func(i);
}

Observation:

In both cases, f() never seems to be “marked for instantiation”, at least not through Sema::
MarkDeclarationReferenced. However, f() does seem to eventually get instantiated in the “Case 2” somehow, whereas it never does in “Case 1”. Any idea?

  1. When does f() get instantiated in “Case 2”?

Case 2 is ill-formed, but the general rule is that MarkDeclarationReferenced will get called when we use that function in a potentially evaluated context, either in non-template code or during template instantiation.

  1. is this connected to lookup somehow?
    I have made some very minor changes in Sema::CppLookupName(), and I’m not sure how this could affect instantiation…

It’s more likely that you haven’t added any logic to permit the instantiation of functions defined within concept maps.

  • Doug

Thanks. That was a lot helpful. However, I do have another question. Consider the following two case scenari:

Case 1:

The previous one, with
(1) f() defined as a concept member, and
(2) func() a restricted template

Case 2:

(1) f() is a global function, and
(2) func() is a simple template, as in

void f() { }

template
void func(T a) {
f(0);
}

int main(int argc, char **argv)
{
int i=0;
func(i);
}

Observation:

In both cases, f() never seems to be “marked for instantiation”, at least not through Sema::
MarkDeclarationReferenced. However, f() does seem to eventually get instantiated in the “Case 2” somehow, whereas it never does in “Case 1”. Any idea?

  1. When does f() get instantiated in “Case 2”?

Case 2 is ill-formed, but the general rule is that MarkDeclarationReferenced will get called when we use that function in a potentially evaluated context, either in non-template code or during template instantiation.

Sorry about this. I meant for the definition of f() to take an integer in as argument…

  1. is this connected to lookup somehow?
    I have made some very minor changes in Sema::CppLookupName(), and I’m not sure how this could affect instantiation…

It’s more likely that you haven’t added any logic to permit the instantiation of functions defined within concept maps.

Any rule of thumb on how to add such logic? Say, for example how is this currently setup for “(corrected) Case 2”?

Please see the handling of function templates in Sema::MarkDeclarationReferenced and the actual instantiation in Sema::InstantiateFunctionDefinition. It probably needs a small tweak for concept maps, but there’s no more advice anyone can give without looking specifically at your representation of concept maps.

  • Doug

Let me see if I can rephrase this problem…
At this point, instantiation is no longer an issue. The issue is managing new DeclContext’s.

Take the following definition of a concept:

concept A {
void f(int) { }
}

This definition is parsed into a ConceptDecl object ContextA where ConceptDecl is a DeclContext as well as a TemplateDecl.
When the concept is used on a generic function such as:

template
requires A
void func(T a) {
f(a);
}

at the call f(a), the lookup process finds the definition of f() in the DeclContext ContextA.
Hence, the definition of func() is accepted.
Let’s refer to the found definition as Fn.

Now, when func() is used in the main, the lookup and instantiation-related processes are still finding and using that same definition of f(): Fn.

However, when compilation is over, and things are being linked together, Fn (of f()) is lost, and can’t be found?
It seems like a fatal assumption is being made about the way that the various lookup* methods in SemaLookup.cpp work and should be used. I just don’t know what those assumptions are…
For one thing, could the use of LookupQualifiedName(…) perhaps entail something else that would affect link time resolution?

That’s wrong. You need to map from the concept’s function f() to the corresponding function f() in the concept map.

  • Doug

Perhaps I should’ve phrased that this is the sample case I’m examining at the moment, not the defacto way that concepts should work. The idea above should generalize to any DeclContext, whether they are ConceptDecls or not.
For instance, forget concepts, call “concept A” My_DeclContext and replace everything as follows:

My_DeclContext A {
void f(int) { }
}
//This definition is parsed into a SomeDecl object ContextA where SomeDecl is a DeclContext.

template<…>
lookup_in (A)
void func(T a) {
f(a);
}

Again, the observations from the previous example should naturally translate to this case, and they are the result of an intensive debugging session (overnight)…

If Fn is found and used throughout the compilation, how come it gets lost at link time?
My interest is that: if we can get this to work, then I’ll know for sure that I understand the way that lookup and instantiation work in clang, with respect to added declaration contexts.

I’m concerned that we are perhaps getting confused from thinking about this problem from completely different perspective. Marcin was also quite ahead of me on this. :slight_smile:
Please, just note that I am not particularly thinking of concepts at the moment… I want to very basically understand why the case above would cause the error I am getting, when the only major change I made was in deviating the lookup process for f() so that it is looked up in ContextA – using LookupQualifiedName(…), rather than in the global scope**…**

I hope this is clearer…

I may actually be onto something at the moment, but I’m not sure if it is going to work. So, any idea would help if it isn’t too much asking. In any event, I’ll continue trying to figure this out…

Thanks,

– Larisse.

Let me see if I can rephrase this problem…
At this point, instantiation is no longer an issue. The issue is managing new DeclContext’s.

Take the following definition of a concept:

concept A {
void f(int) { }
}

This definition is parsed into a ConceptDecl object ContextA where ConceptDecl is a DeclContext as well as a TemplateDecl.
When the concept is used on a generic function such as:

template
requires A
void func(T a) {
f(a);
}

at the call f(a), the lookup process finds the definition of f() in the DeclContext ContextA.
Hence, the definition of func() is accepted.
Let’s refer to the found definition as Fn.

Now, when func() is used in the main, the lookup and instantiation-related processes are still finding and using that same definition of f(): Fn.

That’s wrong. You need to map from the concept’s function f() to the corresponding function f() in the concept map.

  • Doug

Perhaps I should’ve phrased that this is the sample case I’m examining at the moment, not the defacto way that concepts should work. The idea above should generalize to any DeclContext, whether they are ConceptDecls or not.
For instance, forget concepts, call “concept A” My_DeclContext and replace everything as follows:

My_DeclContext A {
void f(int) { }
}
//This definition is parsed into a SomeDecl object ContextA where SomeDecl is a DeclContext.

template<…>
lookup_in (A)
void func(T a) {
f(a);
}

Again, the observations from the previous example should naturally translate to this case, and they are the result of an intensive debugging session (overnight)…

If Fn is found and used throughout the compilation, how come it gets lost at link time?
My interest is that: if we can get this to work, then I’ll know for sure that I understand the way that lookup and instantiation work in clang, with respect to added declaration contexts.

Fn is in a concept, so it can’t be instantiated without instantiating the concept itself first to produce an instantiated Fn declaration. Then you can go ahead and instantiate the body of the instantiated Fn declaration. Step through how a member function of a class template gets instantiated:

template
struct A {
void f(int);
};

It doesn’t make any sense to instantiate A::f(int) without knowing what T is, but that’s what you’re trying to do with concepts.

I’m concerned that we are perhaps getting confused from thinking about this problem from completely different perspective. Marcin was also quite ahead of me on this. :slight_smile:
Please, just note that I am not particularly thinking of concepts at the moment… I want to very basically understand why the case above would cause the error I am getting, when the only major change I made was in deviating the lookup process for f() so that it is looked up in ContextA – using LookupQualifiedName(…), rather than in the global scope**…**

The change you made means that the function declaration “f” that’s being referenced from the instantiation of “func” is still dependent on a template, even though this is non-template code (after instantiation).

  • Doug

Ow. Right. Thanks. I’ll give that a show and let you know…

Things now work, modulo a few tweaks I’m sorting through… :slight_smile:
Thanks a bunch for your help!
– Larisse.