How to perform template substitution in the clang library

Hi all,

Specifically, we have a C++ source file like this:

    template <int n>
    struct N {};

    struct B {
        template <typename M>
        using A = typename std::conditional<std::is_same<M, N<4>>::value,
                                            int*, void*>::type;
    };

    template <typename T, T value>
    struct F : B {};

    template <>
    struct F<decltype(&fopen), &fopen> : B {
        template <typename M>
        using A = double*;
    };

    template <>
    struct F<decltype(&fclose), &fclose> : B {
        template <typename M>
        using A = typename std::conditional<std::is_same<M, N<16>>::value,
                                            void*, char**>::type;
    };

    // More specialization of 'F' follows.

It is easy to find the
[ClassTemplateDecl](http://clang.llvm.org/doxygen/classclang_1_1ClassTemplateDecl.html)s
of `N` and `F`, and the
[QualType](http://clang.llvm.org/doxygen/classclang_1_1QualType.html)
and [FunctionDecl](http://clang.llvm.org/doxygen/classclang_1_1FunctionDecl.html)
of the function pointers `&fopen`, `&fclose`, etc. But the problem is
how to substitute these arguments into N, F and F::A without modifying
the source code.

The question is:

* How do I evaluate `F<decltype(&fprintf), &fprintf>::A<N<4>>` and
know that it is an `int*`?
* How do I evaluate `F<decltype(&fopen), &fopen>::A<N<7>>` and know
that it is a `double*`?
* and so on...

Kind regards
Stewart

Hi all,

I have it on authority that the timing of my previous questions were dismal! So I’ll start the question on a happy Thanksgiving note.

We have a C++ source file like this:

template
struct N {};

struct B {
template
using A = typename std::conditional<std::is_same<M, N<4>>::value,
int*, void*>::type;
};

template <typename T, T value>
struct F : B {};

template <>
struct F<decltype(&fopen), &fopen> : B {
template
using A = double*;
};

template <>
struct F<decltype(&fclose), &fclose> : B {
template
using A = typename std::conditional<std::is_same<M, N<16>>::value,
void*, char**>::type;
};

// More specialization of ‘F’ follows.

It is easy to find the ClassTemplateDecls (http://clang.llvm.org/doxygen/classclang_1_1ClassTemplateDecl.html) of N and F, and the
QualType (http://clang.llvm.org/doxygen/classclang_1_1QualType.html)
and FunctionDecl (http://clang.llvm.org/doxygen/classclang_1_1FunctionDecl.html)
of the function pointers &fopen, &fclose, etc. But the problem is
how to substitute these arguments into N, F and F::A without modifying
the source code.

The question is:

  • How do I evaluate F<decltype(&fprintf), &fprintf>::A<N<4>> and
    know that it is an int*?
  • How do I evaluate F<decltype(&fopen), &fopen>::A<N<7>> and know
    that it is a double*?
  • and so on…

Kind regards
Stewart

Template instantiation requires an active Sema object. The interface to
Sema isn’t stable, and it’s not tremendously well-documented, but
basically you just want to imitiate the series of calls that the parser would
make in order to build up the type expression that you need.

John.

Hi John,

Thanks very much for the pointer!

Kind regards
Stewart

Hi John,

Further to the question:

Kenny said:

I've got a partial solution, the only caveat is that, I can't get
std::is_same<N<4>, N<4>>::value to return true. Well I can live with
that, as I could just define a constexpr method which operates on the
values directly. But I hope someone could provide a correct answer for
this.

I have put the complete solution and the modified input to
https://gist.github.com/4178490.

I've found that to substitute arguments into a class template and
instantiate it, one would:

    Use the arguments to turn the ClassTemplateDecl into
ClassTemplateSpecializationDecl, and
    Instantiate the specialization using Sema::InstantiateClass method.

The method Sema::RequireCompleteType does indirectly call
InstantiateClass, and requires less input, so I call this method
instead. Therefore, we would write:

/**
* Instantiate a class template.
*/
ClassTemplateSpecializationDecl* instantiate(ASTContext& ast, Sema& sema,
                                             DeclContext* parent,
                                             ClassTemplateDecl* decl,
                                             ArrayRef<TemplateArgument> args) {
   void* ins_point;
   auto retval = decl->findSpecialization(args.data(), args.size(), ins_point);
   if (retval == nullptr) {
       retval = ClassTemplateSpecializationDecl::Create(ast, TTK_Class, parent,
                                                        {}, {}, decl,
                                                        args.data(),
args.size(),
                                                        nullptr);
       decl->AddSpecialization(retval, ins_point);
   }
   bool is_incomplete = sema.RequireCompleteType({},
ast.getTypeDeclType(retval),
                                                 diag::err_incomplete_type);
   return is_incomplete ? nullptr : retval;
}

This method only works for ClassTemplateDecl. In the question we also
have a TypeAliasTemplateDecl. For this, I am going to invoke the
TemplateDeclInstantiator directly, since this is the only object which
knows a TypeAliasTemplateDecl. Perhaps this method also works with
ClassTemplateDecl, but I can't be sure as it seems not enough work is
done using TemplateDeclInstantiator alone.

/**
* Instantiate a template alias (`template <...> using Foo = ...`).
*/
TypeAliasDecl* instantiate(ASTContext& ast, Sema& sema, DeclContext* parent,
                           TypeAliasTemplateDecl* decl,
                           ArrayRef<TemplateArgument> args) {
    auto args_count = static_cast<unsigned>(args.size());
    TemplateArgumentList arg_list {TemplateArgumentList::OnStack,
                                   args.data(), args_count};
    MultiLevelTemplateArgumentList multi_arg_list {arg_list};
    TemplateDeclInstantiator instantiator {sema, parent, multi_arg_list};
    auto instantiated = instantiator.Visit(decl);
    if (auto inst_decl = dyn_cast<TypeAliasTemplateDecl>(instantiated)) {
        return inst_decl->getTemplatedDecl();
    }
    return nullptr;
}

(I skipped FunctionTemplateDecl, it is out of the scope of my question.)

Kind regards
Stewart

You definitely don't want to call InstantiateClass directly;
RequireCompleteType is better. More generally, you want to
use the Sema functions to create the type you're interested in.
For a template alias, that will automatically perform the substitution,
because template aliases are not lazily substituted.

For function templates, I would suggest faking up an explicit
instantiation declaration.

John.