Compile Time Template Instantiation Check

Compile time template instantiation check

It's possible to do this if you leverage the fact that specific expressions may or may not be used in places where constexprs are expected, and that you can query to see what the state is for each candidate you have. Specifically in our case, the fact that constexprs with no definition cannot pass as constant expressions and noexcept is a guarantee of constant expressions. Hence, noexcept(...) returning true signals the presence of a properly defined constexpr.

Essentially, this treats constexprs as Yes/No switches, and introduces state at compile-time.

Note that this is pretty much a hack, you will need workarounds for specific compilers (see the articles ahead) and this specific friend-based implementation might be considered ill-formed by future revisions of the standard.

With that out of the way...

User Filip Roséen presents this concept in his article dedicated specifically to it.

His example implementation is, with quoted explanations:

constexpr int flag (int);

A constexpr function can be in either one of two states; either it is
usable in a constant-expression, or it isn't - if it lacks a
definition it automatically falls in the latter category - there is no
other state (unless we consider undefined behavior).

Normally, constexpr functions should be treated exactly as what they
are; functions, but we can also think of them as individual handles to
"variables" having a type similar to bool, where each "variable" can
have one of two values; usable or not-usable.

In our program it helps if you consider flag to be just that; a handle
(not a function). The reason is that we will never actually call flag
in an evaluated context, we are only interested in its current state.

template<class Tag>
struct writer {
friend constexpr int flag (Tag) {
return 0;
}
};

writer is a class template which, upon instantiation, will create a
definition for a function in its surrounding namespace (having the
signature int flag (Tag), where Tag is a template-parameter).

If we, once again, think of constexpr functions as handles to some
variable, we can treat an instantiation of writer as an
unconditional write of the value usable to the variable behind the
function in the friend-declaration.

template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };

I would not be surprised if you think dependent_writer looks like a
rather pointless indirection; why not directly instantiate writer
where we want to use it, instead of going through dependent_writer?

  1. Instantiation of writer must depend on something to prevent immediate instantiation, and;
  2. dependent_writer is used in a context where a value of type bool can be used as dependency.
template<
bool B = noexcept (flag (0)),
int = sizeof (dependent_writer<B>)
>
constexpr int f () {
return B;
}

The above might look a little weird, but it's really quite simple;

  1. will set B = true if flag(0) is a constant-expression, otherwise B = false, and;
  2. implicitly instantiates dependent_writer (sizeof requires a completely-defined type).

The behavior can be expressed with the following pseudo-code:

IF [ `int flag (int)` has not yet been defined ]:
SET `B` = `false`
INSTANTIATE `dependent_writer<false>`
RETURN `false`
ELSE:
SET `B` = `true`
INSTANTIATE `dependent_writer<true>`
RETURN `true`

Finally, the proof of concept:

int main () {
constexpr int a = f ();
constexpr int b = f ();
static_assert (a != b, "fail");
}

I applied this to your particular problem. The idea is to use the constexpr Yes/No switches to indicate whether a type has been instantiated. So, you'll need a separate switch for every type you have.

template<typename T>
struct inst_check_wrapper
{
friend constexpr int inst_flag(inst_check_wrapper<T>);
};

inst_check_wrapper<T> essentially wraps a switch for whatever type you may give it. It's just a generic version of the original example.

template<typename T>
struct writer
{
friend constexpr int inst_flag(inst_check_wrapper<T>)
{
return 0;
}
};

The switch toggler is identical to the one in the original example. It comes up with the definition for the switch of some type that you use. To allow for easy checking, add a helper switch inspector:

template <typename T, bool B = noexcept(inst_flag(inst_check_wrapper<T>()))>
constexpr bool is_instantiated()
{
return B;
}

Finally, the type "registers" itself as initialized. In my case:

template <typename T>
struct MyStruct
{
template <typename T1 = T, int = sizeof(writer<MyStruct<T1>>)>
MyStruct()
{}
};

The switch is turned on as soon as that particular constructor is asked for. Sample:

int main () 
{
static_assert(!is_instantiated<MyStruct<int>>(), "failure");
MyStruct<int> a;
static_assert(is_instantiated<MyStruct<int>>(), "failure");
}

Live on Coliru.

Check at compile-time if an uninstantiated class template inherits from its first template parameter

However, I think that implementing template_parameter_is_base_of should be possible because a compiler should know at compile-time whether a template class inherits from its template parameter or not.

A template is essentially a parameterized tool for manufacturing certain C++ constructs: class, function, or variable. A template, in and of itself, is not yet the thing it will make. A class template does not inherit from anything because it is not a class yet. So the question itself is not a functional one.

Coupled with that is the fact that explicit/partial template specialization exists. Even if the base class template did indeed inherit from its first template parameter, that's no guarantee of anything. You still have no idea if any particular WithParent<T> instantiation of the template will actually use the base template. A user could easily specialize WithParent for a particular type, or even employ partial specialization for a whole family of types.

What you want is not a thing C++ can support. If you're trying to verify something or prevent certain misuse or whatever, you're going to have to do it another way.

Detect the existence of a template instantiation for a given type

Here's a link-time solution. Works on GCC, Clang, and MSVC.

One template (impl::Checker<T>) declares a friend function and calls it.

Another template (impl::Marker) defines that function. If it's not defined, the first class gets an undefined reference.

run on gcc.godbolt.org

#include <cstddef>
#include <type_traits>

namespace impl
{
template <typename T>
struct Checker
{
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
#endif
friend void adl_MarkerFunc(Checker<T>);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif

static std::nullptr_t Check()
{
adl_MarkerFunc(Checker<T>{});
return nullptr;
}

inline static const std::nullptr_t check_var = Check();
static constexpr std::integral_constant<decltype(&check_var), &check_var> use_check_var{};
};

template <typename T>
struct Marker
{
friend void adl_MarkerFunc(Checker<T>) {}
};
}

template <typename T, impl::Checker<T> = impl::Checker<T>{}>
struct Access
{
template <typename U>
void Read()
{
static_assert(std::is_same_v<T, U>);
(void)impl::Marker<U>{};
}
};

int main()
{
Access<int> x;
x.Read<int>();

[[maybe_unused]] Access<float> y; // undefined reference to `impl::adl_MarkerFunc(impl::Checker<float>)'

using T [[maybe_unused]] = Access<double>; // undefined reference to `impl::adl_MarkerFunc(impl::Checker<double>)'
}

Had to introduce a dummy template parameter to Access, since I couldn't think of any other way of detecting it being used in a using.

How to use explicit template instantiation to reduce compilation time?

If you know that your template will be used only for certain types,
lets call them T1,T2, you can move implementation to source file,
like normal classes.

//foo.hpp
template<typename T>
struct Foo {
void f();
};

//foo.cpp
template<typename T>
void Foo<T>::f() {}

template class Foo<T1>;
template class Foo<T2>;

Template instantiation effect on compile duration

Since the definitions of the member functions are all inside the cpp and thus not available to other translation units, the functions won't be implicitly instantiated and thus the cost of compiling the code is limited to the single cpp.

The problem with that approach is that you are limiting the use of your template to the type (or types) for which you provide the manual instantiations. External users cannot instantiate it for other types, if you have to do it, you need to remember to manually specialize for each type you want to use.

There is an alternative, with slightly more cost (not much), but that is generic and faster to compile than the naïve approach. You can provide the template definitions in the header, but instruct the compiler not to implicitly instantiate it for a set of common types, then provide manual instantiations for it in a single translation unit:

// .h
#ifndef TEST_H
#define TEST_H
template <typename T>
class Test
{
public:
T data;
void func() { ... } // definition here
};
extern template class Test<float>; // Declare explicit instantiation for float
extern template class Test<int>; // for int
#endif /* TEST_H */

// cpp
#include "test.h"
template class Test<float>; // explicit instantiation
template class Test<int>;

In this approach the template is visible for instantiations with any type that the user might want to use. But you explicitly tell the compiler not to do the work for a known subset of types for which you provide the specializations. If the user wants Test<std::string> then the compiler will instantiate it implicitly, and that translation unit will pay the price. For translation units that only instantiate Test<float>, Test<int> or that include the header but don't instantiate the template at all, there will be some additional cost (the parser needs to handle the definitions) but there won't be generated code (binary), or time wasted on the optimizer and/or the linker discarding duplicate symbols.

It also implies, as you mention, recompiling all user code that included that header if the contents of the header change.

How to intentionally cause a compile-time error on template instantiation

You could just omit defining it.

template <typename T> struct MyClassTemplate<T*>;

You could also derive from a non-defined specialization

template <typename T> struct invalid;
template <typename T> struct MyClassTemplate<T*> : invalid<T> { };

Note that explicit specializations that declare classes or functions will never depend on template parameters. So, stuff like this that depend on template parameters can't work anyway. In that case, declaring a non-defined explicit specialization should be sufficient

template<> struct MyClassTemplate<int*>;


Related Topics



Leave a reply



Submit