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
What Does "Memory Allocated at Compile Time" Really Mean
Boost::Flat_Map and Its Performance Compared to Map and Unordered_Map
Calling R's Optim Function from Within C++ Using Rcpp
Choosing Embedded Scripting Language for C++
Fatal Error Lnk1104: Cannot Open File 'Libboost_System-Vc110-Mt-Gd-1_51.Lib'
Generate Sha Hash in C++ Using Openssl Library
Downcasting Shared_Ptr<Base> to Shared_Ptr<Derived>
Is There Still a Use for Inline
Multiset, Map and Hash Map Complexity
Converting from Signed Char to Unsigned Char and Back Again
Void_T "Can Implement Concepts"
Will Exit() or an Exception Prevent an End-Of-Scope Destructor from Being Called
Get Function Pointer from Std::Function When Using Std::Bind