Doing a Static_Assert That a Template Type Is Another Template

Doing a static_assert that a template type is another template

You could do something along these lines. Given a trait that can verify whether a class is an instantiation of a class template:

#include <type_traits>

template<typename T, template<typename> class TT>
struct is_instantiation_of : std::false_type { };

template<typename T, template<typename> class TT>
struct is_instantiation_of<TT<T>, TT> : std::true_type { };

Use it as follows in your program:

template<typename T>
struct foo {};

template<typename FooType>
struct bar {
static_assert(is_instantiation_of<FooType, foo>::value, "failure");
};

int main()
{
bar<int> b; // ERROR!
bar<foo<int>> b; // OK!
}

If you want, you could generalize this to detect whether a class is an instance of a template with any number of (type) parameters, like so:

#include <type_traits>

template<template<typename...> class TT, typename T>
struct is_instantiation_of : std::false_type { };

template<template<typename...> class TT, typename... Ts>
struct is_instantiation_of<TT, TT<Ts...>> : std::true_type { };

template<typename FooType>
struct bar {
static_assert(is_instantiation_of<foo, FooType>::value, "failure");
};

You would then use it this way in your program:

template<typename FooType>
struct bar {
static_assert(is_instantiation_of<foo, FooType>::value, "failure");
};

int main()
{
bar<int> b; // ERROR!
bar<foo<int>> b; // OK!
}

Here is a live example.

static_assert in function template with non-type template parameter

A static_assert whose first argument is a non-dependent false constant is always "ill-formed, no diagnostic required", even in a template that is never instantiated. (So neither g++ nor clang++ is "incorrect" here.) In your function template, T is value-dependent but not type-dependent (its type is always int), so decltype(T) is not dependent, and neither is false_type<int>::value.

Could you have your false_type simply also take an int as parameter?

#include <type_traits>
#include <iostream>

template <int>
struct false_type : public std::false_type {};

template <int T>
void function() {
static_assert(false_type<T>::value, "Error");
};

template <>
void function<1>() {
std::cout << 1 << std::endl;
}

int main() {
function<1>();
}

Can I static assert that an entity is instantiation of a template class without enforcing any/all template arguments?

Just make your function a template. N will be deduced automatically.

template<std::size_t N>
auto fun(std::array<A,N>& a) {
// ... do things, which depend on `auto` having deduced a `std::array<A,N>` for some `N`...
}

static_assert and class templates

For B<A<1>> b;, A<1> is only used as template argument, which doesn't cause implicit instantiation of class template A, then the static_assert inside A's definition won't be triggered.

When code refers to a template in context that requires a completely defined type, or when the completeness of the type affects the code, and this particular type has not been explicitly instantiated, implicit instantiation occurs. For example, when an object of this type is constructed, but not when a pointer to this type is constructed.

On the other hand, for A<1> a;, A<1> is required to be a complete type (to construct a), then implicit instantiation happens, static_assert is fired.

EDIT

You can use sizeof (which requires the type to be complete) to cause the implicit instantiation and fire the static_assert. e.g.

template <class T>
class B{
static_assert(sizeof(T) > 0, "static_assert for implicit instantiation");
};

Enforce template type through static_assert

I figured out a better solution for this problem by combining the answers and comments here.

I can define a static type checker like so:

template <class A, class B>
struct CheckTypes
{
static const bool value = false;
};

template <class A>
struct CheckTypes<A, A>
{
static const bool value = true;
};

Not sure if such a struct already exists in the standard library. Anyways, then in Foo, I can check for types and sizes using:

static_assert((sizeof(T) == sizeof(long) || sizeof(T) == sizeof(int)) && !CheckTypes<T, float>::value, "Error!");

Is there a way to static_assert a variable reference given in a template parameter?

If you want the value of myConfig to be used at compile-time, then you should mark it constexpr and give it its value directly in the initializer. Whether it is a static or automatic storage duration variable is then secondary:

constexpr Config myConfig = { .version = 5 };
// alternatively before C++20 for example
// constexpr Config myConfig = { 5 };
/*...*/
Peripheral<myConfig> peripheral;

Then the template should take the parameter by-value, not by-reference, and you should use that template parameter directly in the static_assert:

template<Config config /* , ... */>
struct Peripheral
{
static_assert(config.version > 1, "Config version must be greater than 1");
/* ... */
};

Depending on what else is in Config or if you are not using C++20 or later, the type might not be allowed as by-value template parameter. In that case you can keep using a reference parameter (although I am not sure that this is good design) but you would need to make it const Config& instead to match the const implied by constexpr. In this case it does matter that myConfig has static storage duration (specifically it may be declared at namespace scope, as a static data member or since C++17 as static local variable).

If you want to keep on using a local config_ copy to assert on, that copy should also be marked constexpr (const is not enough to make a variable usable at compile-time) and hence must also be marked static (because non-static data members cannot be declared constexpr).

The value of the template parameter and static_assert cannot be (potentially) determined at run-time, so myConfig.version = 5; will never work.

A safe, standard-compliant way to make a class template specialization fail to compile using `static_assert` only if it is instantiated?

Static assertions are there to be used directly in the class without doing anything complicated.

#include <type_traits>

template<typename T>
struct OnlyNumbers {
static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
// ....
};

In some cases, you might get additional error messages since instanciating OnlyNumbers for non-arithmetic types might cause more compilation errors.

One trick I have used from time to time is

#include <type_traits>

template<typename T>
struct OnlyNumbers {
static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
using TT = std::conditional_t<std::is_arithmetic_v<T>,T,int>;
// ....
};

In this case, your class gets instanciated with int, a valid type. Since the static assertion fails anyway, this does not have negative effects.

Allow Only Explicit Specialization of Template Class

There are a few ways how you could accomplish this.

1. Explicitly list the acceptable enums

One way would be to explicitly list the acceptable enums in your static_assert:

godbolt

#include <type_traits>

template<class T, class... Other>
constexpr bool is_same_one_of = (std::is_same_v<T, Other> || ...);

enum Enum1 {};
enum Enum2 {};
enum Enum3 {};

template<class T>
class kvEnumHelper {
static_assert(
is_same_one_of<T, Enum1, Enum2 /* , ... more enum types ... */>,
"T must be either Enum1 or Enum2"
);

/* ... actual implementation ... */
};

kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error


2. Inherit implementation

Another option would be to move the actual implementation into a separate class and only make the specializations inherit from the implementation class.

godbolt

#include <type_traits> 

enum Enum1 {};
enum Enum2 {};
enum Enum3 {};

template<class T>
class kvEnumHelper {
static_assert(
!std::is_same_v<T, T>, // always false, but only when actually instanciated
"Enum Class is not supported"
);
};

template<class TEnum>
class kvEnumHelperImpl {
/* ... actual implementation ... */
};

template<> class kvEnumHelper<Enum1> : public kvEnumHelperImpl<Enum1> {};
template<> class kvEnumHelper<Enum2> : public kvEnumHelperImpl<Enum2> {};

kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error


3. Using an additional trait

Yet another alternative would be to use a trait that can specialized for the enum types you would want to be usable with kvEnumHelper.

godbolt

template <class T>
constexpr bool allow_enum_helper = false;

enum Enum1 {};
enum Enum2 {};
enum Enum3 {};

template<class T>
class kvEnumHelper {
static_assert(
allow_enum_helper<T>,
"Enum Class is not supported"
);

/* ... actual implementation ... */
};

template<>
constexpr bool allow_enum_helper<Enum1> = true;
template<>
constexpr bool allow_enum_helper<Enum2> = true;

kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error

If you already have a function like getEnumFromString that is deleted and has specializations for the allowable enum types you could use that to detect if kvEnumHelper<T> should be allowed by detecting if the function is deleted or not.

godbolt

#include <string>

enum Enum1 {};
enum Enum2 {};
enum Enum3 {};

template<typename T>
T getEnumFromString(const std::string& in_string) = delete; // only allow templates we define (catches them at compile time)
template<> Enum1 getEnumFromString(const std::string& in_string);
template<> Enum2 getEnumFromString(const std::string& in_string);

template<class T>
constexpr bool allow_enum_helper = requires { getEnumFromString<T>(std::string{}); };

template<class T>
class kvEnumHelper {
static_assert(
allow_enum_helper<T>,
"Enum Class is not supported"
);

/* ... actual implementation ... */
};

kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error


Related Topics



Leave a reply



Submit