C++ Template Template Instantiation - c++

I have the following encapsulation code for variadic parameter packs.
template <typename... Args>
struct pack
{
};
template <template <typename... Args> class ENCAP, typename... Args>
struct encapsulate_arguments
{
typedef pack<ENCAP<Args>...> type;
};
template <template <typename... Args> class ENCAP, typename... Args>
struct encapsulate_arguments<ENCAP, pack<Args...>>
{
typedef pack<ENCAP<Args>...> type;
};
template <typename L>
struct Master
{
template <typename T>
struct Slave
{
typedef T type;
};
};
This works fine for encapsulating variadic packs such as:
typedef encapsulate_arguments<Master<float>::Slave, double, int>::type foo;
or
typedef encapsulate_arguments<Master<float>::Slave, pack<double, int>>::type foo;
or
typedef encapsulate_arguments<std::vector, pack<double, int>>::type foo;
where it is not dependent on another template parameter - resulting in the following being defined :
pack<Master<float>::Slave<double>, Master<float>::Slave<int>>
or
pack<std::vector<double>, std::vector<int>>
The problem is that if I want to make the encapsulation template parameter ENCAP type dependent I can't get it to compile:
template <typename L>
struct Other
{
// ARGGH!!!
// typedef encapsulate_arguments<Master<L>::Slave, pack<double, int>>::type EmbeddedType;
};
http://ideone.com/ZwfVaU
Is this even possible and / or how can I make this work?

You're missing a typename and a template:
typedef typename encapsulate_arguments<
// ^^^^^^^^
Master<L>::template Slave, pack<double, int>
// ^^^^^^^^
>::type EmbeddedType;
Demo

Related

Is it possible to capture a type template into a template argument?

Is it possible to capture a template from a template argument i.e. have a nested template specifier with a template argument that holds the template type?
template< typename T, typename Label = std::string>
class foo {
// ...
};
template <
template < typename C, typename T > typename C<T>,
// ...
typename Label = std::string
>
class bar {
// ...
C< foo< T, Label > > A;
};
For instance, I'd like to pass a generic STL container (std::vector< int >) as template argument, but declare a member of the same meta-type (std::vector) but with different value type (foo< int >) i.e. std::vector< foo< int > >. It may seem convoluted, but it would be helpful to not hardcode the type of STL container.
For context, I'm aiming at a generic container adapter/wrapper (in the line of std::stack or std::queue) that provides some higher-level functionality.
Yes, you can just use template specialization:
#include <string>
template<typename T, typename Label = std::string>
class foo {};
template <class T, typename Label = std::string>
class bar;
template <template<class...> class C, typename T, typename Label>
class bar<C<T>, Label> {
C<foo<T, Label>> A;
};
Demo.
The other answer's approach can be generalized as a reusable template rebinder:
template<typename T>
struct rebinder;
template<template<typename...> typename T, typename... Args>
struct rebinder<T<Args...>> {
template<typename... Us>
using rebind = T<Us...>;
};
template<typename T, typename... Us>
using rebound = rebinder<T>::template rebind<Us...>;
// example:
#include <vector>
template<typename T>
struct MyStruct {
rebound<T, float> vec;
};
int main() {
MyStruct<std::vector<int>> x;
static_assert(std::is_same_v<std::vector<float>, decltype(x.vec)>);
}
see on godbolt

Concatenating tuples as types

I'm trying to practice some template programming. Maybe there's a standard way to do this, and I would be thankful for such answers, but my main goal is to practice the template programming techniques, so I tried to implement it myself:
I need to concatenate multiple tuples, but as types, not like std::cat_tuple does it. So I need something like cat<std::tuple<int, float>, std::tuple<char, bool>, ...> to get std::tuple<int, float, char, bool, ...> as a type.
My current attempt failed with a is not a template error:
/* Concat tuples as types: */
template <typename first_t, typename... rest_t> struct cat {
using type = typename _cat<first_t, typename cat<rest_t...>::type>::type;
^^^^ cat is not a template
};
template <typename first_t, typename second_t>
struct cat<first_t, second_t> {
using type = typename _cat<first_t, second_t>::type;
^^^^ cat is not a template
};
// Concat two tuples:
template <typename, typename> struct _cat;
template <typename tuple_t, typename first_t, typename... rest_t>
struct _cat<tuple_t, std::tuple<first_t, rest_t...>> {
using type = typename _cat<typename append<first_t, tuple_t>::type, std::tuple<rest_t...>>::type;
};
template <typename tuple_t, typename first_t>
struct _cat<tuple_t, std::tuple<first_t>> {
using type = typename append<first_t, tuple_t>::type;
};
// Prepend element to tuple:
template <typename, typename> struct prepend;
template <typename elem_t, typename... tuple_elem_t>
struct prepend<elem_t, std::tuple<tuple_elem_t...>> {
using type = std::tuple<elem_t, tuple_elem_t...>;
};
// Apppend element to tuple:
template <typename, typename> struct append;
template <typename elem_t, typename... tuple_elem_t>
struct append<elem_t, std::tuple<tuple_elem_t...>> {
using type = std::tuple<tuple_elem_t..., elem_t>;
};
What may be causing the error?
Is this a good approach? It might be solved in a simpler way, but I wanted it to be multi-purpose (with the append/prepend operations etc.).
How about a one-liner direct template aliase:
template<typename ... input_t>
using tuple_cat_t=
decltype(std::tuple_cat(
std::declval<input_t>()...
));
tuple_cat_t<
std::tuple<int,float>,
std::tuple<int>
> test{1,1.0f,2};
After reordering the definition a little, your code works fine.
I don't think that there are any guidelines for template meta programming. Probably due to the fact that the C++ committee is enhancing TMP "aggressively" and too few people is using TMP.
Here is my version of Cat, it basically follows the same structure as yours:
template <class, class>
struct Cat;
template <class... First, class... Second>
struct Cat<std::tuple<First...>, std::tuple<Second...>> {
using type = std::tuple<First..., Second...>;
};
Is too late to play?
I propose the following solution
template <typename T, typename ...>
struct cat
{ using type = T; };
template <template <typename ...> class C,
typename ... Ts1, typename ... Ts2, typename ... Ts3>
struct cat<C<Ts1...>, C<Ts2...>, Ts3...>
: public cat<C<Ts1..., Ts2...>, Ts3...>
{ };
Observe that this solution doesn't works only with a variadic list of std::tuple but also with a generic container of types. If a std::tuple-only solution is enough for you, you can simplify it as follows
template <typename T, typename ...>
struct cat
{ using type = T; };
template <typename ... Ts1, typename ... Ts2, typename ... Ts3>
struct cat<std::tuple<Ts1...>, std::tuple<Ts2...>, Ts3...>
: public cat<std::tuple<Ts1..., Ts2...>, Ts3...>
{ };
You can test that works with
using t1 = typename cat<std::tuple<int, float>,
std::tuple<char, bool>,
std::tuple<long, char, double>>::type;
using t2 = std::tuple<int, float, char, bool, long, char, double>;
static_assert(std::is_same<t1, t2>::value, "!");
-- EDIT --
As pointed by felix (thanks!) with my precedent solution we have that
std::is_same<int, typename cat<int>::type>::value == true
that is... cat<T>::type is defined also when T isn't a std::tuple.
This is a problem?
I don't know because I don't know how is used cat<T>::type.
Anyway... avoid it imposing that cat<Ts...>::type is defined only when all Ts... are type containers (with the same container), it's simple: the main version for cat become only declared but not defined
template <typename, typename ...> // or also template <typename...>
struct cat;
and is introduced an additional specialization with a single type (but only when it's a type container).
template <template <typename ...> class C, typename ... Ts1>
struct cat<C<Ts1...>>
{ using type = C<Ts1...>; };

Recursively folding a parameter pack to resolve placeholder types

Notice: Followup to this question
After asking this question about parameter pack folding into pairs, I noticed that I need to retain the complete type of the previously folded type as the left pair type.
For example:
Fold<char, int, long, double> f;
must evaluate to
std::tuple<
std::pair<char
, int>,
std::pair<std::pair<char, int> /* <-- the previous resulting type */
, long>,
std::pair<std::pair<std::pair<char, int>, long> /* the previous type again */
, double>
> f;
Context to this problem
The reason why I need this, is because the types which have to be "folded" can be placeholder types. The placeholders "real" type can only be known when both having the fully expanded type to the left as well as the unexpanded type.
The leftmost type never contains placeholders.
Let me illustrate this with a quick example:
struct CopyTypeFromPreviousArgumentTag { };
template<typename T = CopyTypeFromPreviousArgumentTag>
struct Foo;
template<typename T...>
struct Bar {
/* Here fold will not use std::pair, but a resolver type that takes both A and B and gives back the resolved B */
Fold<T...> expanded;
};
Now Bar can be used like this:
Bar< Foo<int>
, Foo<>
, Foo<>
, Foo<double>
, Foo<>
> f;
and the internal type decltype(f::expanded) will be:
std::tuple< Foo<int>
, Foo<int>
, Foo<int>
, Foo<double>
, Foo<double>
>;
EDIT: The Bar class is actually not restricted to any class type it might hold. It can be a mixture of several types. So see the Foo class as a placeholder for some type Foo where a resolver type traits exists given the previous resolved type: ResolveType<PreviouslyResolvedType, CurrentType>::Type will give the resolved type correctly. Hence the std::pair idiom.
My current attempt
I tried to implement the recursion by building upon the answer from the linked question, but can't get it to work.
namespace Detail {
template<typename, typename...>
struct Fold;
template
< size_t... Indices
, typename... Types
> struct Fold<std::index_sequence<Indices...>, Types...> {
using Tuple = std::tuple<Types...>;
using Type = std::tuple<std::pair /* use std::pair just to match the first example */
//< std::tuple_element_t<Indices, Tuple>
< typename Fold
< std::tuple_element_t<Indices, Tuple>
, std::make_index_sequence<Indices>
, Types...>::Type; /* Tuple can't be expanded :( */
, std::tuple_element_t<Indices + 1, Tuple>
>::Type...>;
};
} /* namespace Detail */
template<typename... Types>
using Fold = typename Detail::Fold<std::make_index_sequence<sizeof...(Types) - 1>, Types...>::Type;
The linked question is a very convoluted way of doing this. If it was a runtime problem, it will obviously be solved with a one-pass algorithm, metaprogramming is no different.
struct copy_prev {};
template<typename T = copy_prev>
struct Foo {};
template<typename... Ts, typename T>
auto operator+(std::tuple<Ts...>, Foo<T>)
-> std::tuple<Ts..., Foo<T>>;
template<typename... Ts>
auto operator+(std::tuple<Ts...> t, Foo<copy_prev>)
-> std::tuple<Ts..., select_last_t<Ts...>>;
template<typename... Ts>
using fold_t = decltype((std::tuple<>{} + ... + std::declval<Ts>()));
Where select_last_t is implemented as
template<typename T>
struct tag
{
using type = T;
};
template<typename... Ts>
struct select_last
{
using type = typename decltype((tag<Ts>{}, ...))::type;
};
template<typename... Ts>
using select_last_t = typename select_last<Ts...>::type;
Live
Not sure to understand what do you want... but if your Bar struct accept only Foo types... that is: if can be written as follows
template <typename ...>
struct Bar;
template <typename ...Ts>
struct Bar<Foo<Ts>...>
{ /* something */ };
and in Bar you want a type so that from
Bar<Foo<int>, Foo<>, Foo<>, Foo<double>, Foo<>>
you want the internal type
std::tuple<Foo<int>, Foo<int>, Foo<int>, Foo<double>, Foo<double>>
where the unexpressed (defaulted) argument for Foo are substituted with the last expressed argument... I don't see an elegant solution.
The best I can imagine is the development of a type helper as follows (where, for the sake of brevity, I've renamed ctfpat the former CopyTypeFromPreviousArgumentTag)
template <typename...>
struct fooFolder;
// error case: the first type of Bar is ctfpat (non impemented;
// generate compile time error)
template <typename ... Ts>
struct fooFolder<std::tuple<>, ctfpat, ctfpat, Ts...>;
template <typename ... Tps, typename Tprev, typename T0, typename ... Ts>
struct fooFolder<std::tuple<Tps...>, Tprev, T0, Ts...>
: fooFolder<std::tuple<Tps..., Foo<T0>>, T0, Ts...>
{ };
template <typename ... Tps, typename Tprev, typename ... Ts>
struct fooFolder<std::tuple<Tps...>, Tprev, ctfpat, Ts...>
: fooFolder<std::tuple<Tps..., Foo<Tprev>>, Tprev, Ts...>
{ };
template <typename Tpl, typename Tprev>
struct fooFolder<Tpl, Tprev>
{ using type = Tpl; };
and Bar become
template <typename ...>
struct Bar;
template <typename ...Ts>
struct Bar<Foo<Ts>...>
{
using foldedType = typename fooFolder<std::tuple<>, ctfpat, Ts...>::type;
foldedType expanded;
};
The following is a full compiling example
#include <tuple>
#include <type_traits>
struct ctfpat // copy type from previous argument tag
{ };
template <typename T = ctfpat>
struct Foo
{ };
template <typename ...>
struct fooFolder;
// error case: the first type of Bar is ctfpat (non impemented;
// generate compile time error)
template <typename ... Ts>
struct fooFolder<std::tuple<>, ctfpat, ctfpat, Ts...>;
template <typename ... Tps, typename Tprev, typename T0, typename ... Ts>
struct fooFolder<std::tuple<Tps...>, Tprev, T0, Ts...>
: fooFolder<std::tuple<Tps..., Foo<T0>>, T0, Ts...>
{ };
template <typename ... Tps, typename Tprev, typename ... Ts>
struct fooFolder<std::tuple<Tps...>, Tprev, ctfpat, Ts...>
: fooFolder<std::tuple<Tps..., Foo<Tprev>>, Tprev, Ts...>
{ };
template <typename Tpl, typename Tprev>
struct fooFolder<Tpl, Tprev>
{ using type = Tpl; };
template <typename ...>
struct Bar;
template <typename ... Ts>
struct Bar<Foo<Ts>...>
{
using foldedType = typename fooFolder<std::tuple<>, ctfpat, Ts...>::type;
foldedType expanded;
};
int main()
{
using t1 = typename Bar<Foo<>, Foo<int>, Foo<>, Foo<double>,
Foo<>>::foldedType;
using t2 = std::tuple<Foo<int>, Foo<int>, Foo<int>, Foo<double>,
Foo<double>>;
static_assert( std::is_same<t1, t2>{}, "!" );
}
I start my solution by building something that knows how to create the nth type. It's just a pair of the previous created type, and the current source type, so:
template <std::size_t I, class T>
struct element {
using type = std::pair<typename element<I - 1, T>::type,
std::tuple_element_t<I + 1, T>>;
};
template <class T>
struct element<0, T> {
using type =
std::pair<std::tuple_element_t<0, T>, std::tuple_element_t<1, T>>;
};
All we really need to do now is put the input types into a tuple, grab an integer pack, and feed the tuple and unfold the integer pack through element, into a new tuple. No problem:
template <class I, class... Ts>
class fold_helper;
template <std::size_t... Is, class... Ts>
class fold_helper<std::index_sequence<Is...>, Ts...> {
using tup = std::tuple<Ts...>;
public:
using type = std::tuple<typename element<Is, tup>::type...>;
};
template <class... Ts>
using Fold = typename fold_helper<std::make_index_sequence<sizeof...(Ts)-1>,
Ts...>::type;
Finally, let's check this works:
int main() {
static_assert(
std::is_same<Fold<char, int, long, double>,
std::tuple<std::pair<char, int>, //
std::pair<std::pair<char, int>, long>,
std::pair<std::pair<std::pair<char, int>, long>,
double>>>::value,
"");
return 0;
};
This program compiled for me.

Trouble with syntax for template-template-templates

I'm writing a meta-function MultipartitionWithUnaryPredicates, used in the form
MultipartitionWithUnaryPredicates<Pack<Args...>, UnaryPredicates...>::type
so that a template pack of types will be multipartitioned by a pack UnaryPredicates... of unary predicates, with the partitions in the order of the unary predicates listed. If you're not sure what I mean, just check out main() in the code below (which works correctly):
#include <iostream>
#include <type_traits>
#include <typeInfo>
template <template <typename> class, typename, typename, typename> struct Helper;
template <template <typename> class UnaryPredicate, template <typename...> class P, typename... Types1, typename... Types2>
struct Helper<UnaryPredicate, P<>, P<Types1...>, P<Types2...>> {
using type = P<Types1..., Types2...>;
using head = P<Types1...>;
using tail = P<Types2...>;
};
template <template <typename> class UnaryPredicate, template <typename...> class P, typename First, typename... Rest, typename... Types1, typename... Types2>
struct Helper<UnaryPredicate, P<First, Rest...>, P<Types1...>, P<Types2...>> : std::conditional<UnaryPredicate<First>::value,
Helper<UnaryPredicate, P<Rest...>, P<Types1..., First>, P<Types2...>>,
Helper<UnaryPredicate, P<Rest...>, P<Types1...>, P<Types2..., First>>
>::type {};
template <typename, template <typename> class> struct PartitionWithUnaryPredicate;
template <template <typename> class UnaryPredicate, template <typename...> class P, typename... Ts>
struct PartitionWithUnaryPredicate<P<Ts...>, UnaryPredicate> : Helper<UnaryPredicate, P<Ts...>, P<>, P<>> {};
template <typename, template <typename> class...> struct MultipartitionWithUnaryPredicates;
template <typename Pack, template <typename> class UnaryPredicate>
struct MultipartitionWithUnaryPredicates<Pack, UnaryPredicate> : PartitionWithUnaryPredicate<Pack, UnaryPredicate> {};
template <typename, typename> struct Join;
template <template <typename...> class P, typename... Types1, typename... Types2>
struct Join<P<Types1...>, P<Types2...>> {
using type = P<Types1..., Types2...>;
};
//template <template <typename, template <typename> class> class Pack, typename... Ts>
//struct JoinSpecial : Join<typename Pack::head, typename MultipartitionWithUnaryPredicates<typename Pack::tail, Ts...>::type> {};
template <typename Pack, template <typename> class First, template <typename> class... Rest>
struct MultipartitionWithUnaryPredicates<Pack, First, Rest...> : Join<typename PartitionWithUnaryPredicate<Pack, First>::head, typename MultipartitionWithUnaryPredicates<typename PartitionWithUnaryPredicate<Pack, First>::tail, Rest...>::type> {};
// The above can be improved, since PartitionWithUnaryPredicate<Pack, First> is being computed twice.
// -----------------------------------------------------------------------------------------------------------------------------------------------
// Testing:
template <typename...> struct Pack {};
template <typename Last>
struct Pack<Last> {
static void print() {std::cout << typeid(Last).name() << std::endl;}
};
template <typename First, typename ... Rest>
struct Pack<First, Rest...> {
static void print() {std::cout << typeid(First).name() << ' '; Pack<Rest...>::print();}
};
struct Thing {};
struct Blob { Blob(Blob&&){} }; // Copy constructor deleted.
struct Object {};
enum MyEnum {x, y, z};
enum HerEnum {xx, yy, zz};
int main() {
MultipartitionWithUnaryPredicates<Pack<int, Thing, double, short, Blob, char, MyEnum, long, Object, float, HerEnum>,
std::is_integral>::type b;
b.print(); // int short char long Thing double Blob MyEnum Object float HerEnum
MultipartitionWithUnaryPredicates<Pack<int, Thing, double, short, Blob, char, MyEnum, long, Object, float, HerEnum>,
std::is_integral, std::is_enum>::type c;
c.print(); // int short char long MyEnum HerEnum Thing double Blob Object float
MultipartitionWithUnaryPredicates<Pack<int, Thing, double, short, Blob, char, MyEnum, long, Object, float, HerEnum>,
std::is_integral, std::is_enum, std::is_arithmetic>::type d;
d.print(); // int short char long MyEnum HerEnum double float Thing Blob Object
MultipartitionWithUnaryPredicates<Pack<int, Thing, double, short, Blob, char, MyEnum, long, Object, float, HerEnum>,
std::is_integral, std::is_enum, std::is_arithmetic, std::is_member_pointer, std::is_copy_constructible>::type e;
e.print(); // int short char long MyEnum HerEnum double float Thing Object Blob
}
The problem is trying to refine the above code.
PartitionWithUnaryPredicate<Pack, First>
is being computed twice, I'm trying to define JoinSpecial that will use it just once. But I can't get the syntax correct.
template <template <typename, template <typename> class> class Pack, typename... Ts>
struct JoinSpecial : Join<typename Pack::head, typename MultipartitionWithUnaryPredicates<typename Pack::tail, Ts...>::type> {};
does not compile. The template type PartitionWithUnaryPredicate is of type template <typename, template <typename> class> class is it not?
Update:
Thanks to Angew's tip, I got the syntax correct now:
template <template <typename, template <typename> class> class P, typename Pack, template <typename> class Pred, template <typename> class... Ts>
struct JoinSpecial : Join<typename P<Pack, Pred>::head, typename MultipartitionWithUnaryPredicates<typename P<Pack, Pred>::tail, Ts...>::type> {};
template <typename Pack, template <typename> class First, template <typename> class... Rest>
struct MultipartitionWithUnaryPredicates<Pack, First, Rest...> : JoinSpecial<PartitionWithUnaryPredicate, Pack, First, Rest...> {};
Now everything works correctly--my first time using template-template-templates, which I must say looks quite ugly.
In your definition of JoinSpecial, Pack is a class template. Then, in specifying template arguments to Join, you have typename Pack::head and typename Pack::tail. But Pack is a template, not a class — you need to supply template arguments for Pack.

How can you statically check that a type T exists in a variadic template parameter list

I am trying to statically check to see if a type exists in a variadic template parameter list. However, this template list actually exists within a class that is passed a single type. The answer here shows how to check a list of parameters or a parameter pack, but I am unsure how to test a class that contains variadic templates.
For example
template <typename ...S>
class Services {};
template <typename Services>
class ServiceLocator
{
public:
template <typename T>
T& Resolve()
{
static_assert( check_t_exists_in_variadic_template_within_Services );
return Find<T>();
}
};
What could I write in this static_assert to ensure that each call to this service locator is checked and a compiler error thrown if Resolve is called with a type that does not exist in the template parameter list inside Services?
What I am specicially after is something along the lines of:
static_assert(is_any<T,Services::S...>::value, "T does not exist in Services::S");
Based on François' answer, here's a shorter version that avoids usage of std::tuple and uses std::integral_constant (via true/false_type) and provides a C++14-style contains_v alias. The general idea is the same though.
template <typename T, typename... Args>
struct contains;
template <typename T>
struct contains<T> : std::false_type {};
template <typename T, typename... Args>
struct contains<T, T, Args...> : std::true_type {};
template <typename T, typename A, typename... Args>
struct contains<T, A, Args...> : contains<T, Args...> {};
template <typename T, typename... Args>
constexpr bool contains_v = contains<T, Args...>::value;
You can use it like this:
static_assert(contains_v<float, float, double>,
"failure: float not among <float, double>"); // does not trigger
static_assert(contains_v<int, float, double>,
"failure: int not among <float, double>"); // triggers
One issue with your current code is that ServiceLocator takes a concrete type so you lose the template parameters passed to Services. To retrieve them you need to typedef it somehow so I chose std::tuple since you can't typedef the variadic list directly.
#include <tuple>
#include <type_traits>
template <typename Type, typename Collection>
struct contains;
template <typename Type>
struct contains<Type, std::tuple<>>
{
typedef std::false_type result;
};
template <typename Type, typename ... Others>
struct contains<Type, std::tuple<Type, Others...>>
{
typedef std::true_type result;
};
template <typename Type, typename First, typename ... Others>
struct contains<Type, std::tuple<First, Others...>>
{
typedef typename contains<Type, std::tuple<Others...>>::result result;
};
template <typename ... S>
struct Services
{
typedef std::tuple<S...> Collection;
};
template <typename ServicesType>
class ServiceLocator
{
public:
template <typename T>
T * Resolve()
{
static_assert(contains<T, typename ServicesType::Collection>::result::value, "Fail");
return nullptr;
}
};
class S1 {};
class S2 {};
class S3 {};
int main(int /*argc*/, char * /*argv*/[])
{
Services< S1, S2 > services;
ServiceLocator< decltype(services) > locator;
locator.Resolve< S1 >();
locator.Resolve< S2 >();
locator.Resolve< S3 >(); // triggers static_assert
return 0;
}
I only checked with clang but I hope this helps.
Here is a method using constexpr:
#include <type_traits>
template <typename T>
constexpr bool contains() {
return false;
}
template <typename T, typename A, typename... Tail>
constexpr bool contains() {
return std::is_same<T, A>::value ? true : contains<T, Tail...>();
}
int main()
{
// usage: contains<type_you_want_to_check, type_1, type_2,...>()
static_assert(contains<float, int, double, float>(), "does contain float");
static_assert(contains<float, int, double, char>(), "does not contain float");
}
Aside from being simpler and easier to understand (IMO), this method has the advantage of being easily extensible to other needs, as you can replace the std::is_same call with any other constexpr bool expression, such as std::is_base_of in order to check if the parameter pack contains any base or derived types.