Is it possible to pass a class template (like std::vector, not instantiating it like std::vector<int>) as template argument? I want to write a type that checks whether a given type is an instance of a given template. I know that the compiler doesn't allow to pass an uninstantiated template as-is but I wonder if there is a better workaround than what I got.
My implementation (note that I erase TArgs at the very bottom):
#include <type_traits>
template <typename Instance, typename Template>
struct IsInstanceOf : std::false_type {};
template <
template <typename...> typename Instance,
template <typename...> typename Template,
typename... IArgs,
typename... TArgs>
struct IsInstanceOf<Instance<IArgs...>, Template<TArgs...>>
: std::is_same<Instance<IArgs...>, Template<IArgs...>> {};
This implementation works, but I have to instantiate the template with some type, ex:
IsInstanceOf<std::vector<float>, std::vector<void>>::value
The behavior is as expected but I wonder if there is any better, like
IsInstanceOf<std::vector<float>, std::vector<>>::value
// since this is illegal
IsInstanceOf<std::vector<float>, std::vector>::value
Here is a link to an example.
#include <type_traits>
template <typename T, template <typename...> typename Template>
struct IsInstanceOf : std::false_type {};
template <
template <typename...> typename Template,
typename... TArgs>
struct IsInstanceOf<Template<TArgs...>, Template>
: std::true_type {};
#include <vector>
static_assert(IsInstanceOf<std::vector<float>, std::vector>::value);
static_assert(!IsInstanceOf<int, std::vector>::value);
#include <string>
static_assert(!IsInstanceOf<std::string, std::vector>::value);
static_assert(IsInstanceOf<std::string, std::basic_string>::value);
int main() {}
https://wandbox.org/permlink/PTXl0KoxoJ2aFJfK
Related
I try to write a metafunction type_par_same_as that selects true_type whenever the template parameter(s) of a class match the given types:
Demo
#include <type_traits>
#include <concepts>
#include <string>
#include <vector>
#include <cstdio>
template <template <typename...> class Template, typename T>
struct type_par_same_as_impl : std::false_type {};
template <template <typename...> class Template, typename... Args>
struct type_par_same_as_impl<Template<Args...>, Args...> : std::true_type {};
template <template <typename...> class Template, typename... Args>
concept type_par_same_as = type_par_same_as_impl<Template, Args...>::value;
int main()
{
std::vector<int> vint;
std::vector<std::string> vstring;
if constexpr (type_par_same_as<decltype(vint), int>) {
printf("Vector instantiated with type int!\n");
}
}
Here's what I'm getting:
<source>:11:56: error: type/value mismatch at argument 1 in template parameter list for 'template<template<class ...> class Template, class T> struct type_par_same_as_impl'
11 | struct type_par_same_as_impl<Template<Args...>, Args...> : std::true_type {};
| ^
<source>:11:56: note: expected a class template, got 'Template<Args ...>'
My approach was that a raw template template parameter just takes in any specialization of a template, say e.g. std::vector (without type). Then I SFINAE-out class types that don't match the template specialization Template<Args...> (e.g. std::vector<int> in case int was given as Args) and I should receive true_type for all class types where this can be done. But my logic seems to be off at some point. Where did I go wrong?
Here's how to make it compile:
template <class Template, typename... T>
struct type_par_same_as_impl : std::false_type {};
template <template <typename...> class Template, typename... Args>
struct type_par_same_as_impl<Template<Args...>, Args...> : std::true_type {};
template <class Template, typename... Args>
concept type_par_same_as = type_par_same_as_impl<Template, Args...>::value;
But it won't work with your test case as std::vector<int> is really std::vector<int, std::allocator<int>> and you're passing only the int of the two. So you need:
int main()
{
std::vector<int> vint;
std::vector<std::string> vstring;
if constexpr (type_par_same_as<decltype(vint), int, std::allocator<int>>) {
printf("Vector instantiated with type int!\n");
}
}
This can work as originally intended, if, expanding on lorro's answer, Args... are placed in a non-deduced context so they're only deduced from the explicitly passed template parameters, and not the parameters which which std::vector is instantiated, by making use of std::type_identity:
#include <type_traits>
#include <vector>
template <typename Template, typename... Args>
struct type_par_same_as_impl : std::false_type {};
template <template <typename...> typename Template, typename... Args>
struct type_par_same_as_impl<Template<std::type_identity_t<Args>...>, Args...>
: std::true_type {};
template <typename Template, typename... Args>
concept type_par_same_as = type_par_same_as_impl<Template, Args...>::value;
static_assert(type_par_same_as<std::vector<int>, int, std::allocator<int>>);
static_assert(type_par_same_as<std::vector<int>, int>);
static_assert(not type_par_same_as<std::vector<int>, float>);
Try it on Compiler Explorer
I just wanted to add a solution that I found just now which is possibly a bit more versatile.
Instead of comparing the first template parameter one might as well extract the nth template parameter and use the idiomatic std::is_same<U,T> to compare it. This way the user has the freedom to choose which template parameter is actually compared:
Demo
#include <type_traits>
#include <concepts>
#include <string>
#include <vector>
#include <tuple>
#include <cstdio>
template<std::size_t, typename>
struct nth_targ_of;
template<std::size_t N, template <typename...> class Template, typename... Args>
struct nth_targ_of<N, Template<Args...>> : std::tuple_element<N, std::tuple<Args...>> {};
template<std::size_t N, typename T>
using nth_targ_of_t = nth_targ_of<N, T>::type;
int main()
{
std::vector<int> vint;
std::vector<std::string> vstring;
if constexpr(std::same_as<nth_targ_of_t<0, decltype(vint)>, int>) {
printf("Vector instantiated with type int!\n");
}
if constexpr(std::same_as<nth_targ_of_t<0, decltype(vstring)>, std::string>) {
printf("Vector instantiated with type string!\n");
}
}
Output:
Vector instantiated with type int!
Vector instantiated with type string!
consider this example:
#include <iostream>
template <typename T, std::size_t I>
struct Foo
{
};
template <typename T>
struct specializes_foo : std::false_type {};
template <typename T, std::size_t I>
struct specializes_foo<Foo<T, I>> : std::true_type {};
template <typename T>
concept foo = specializes_foo<T>::value;
int main()
{
std::cout << foo<int> << "\n";
std::cout << foo<Foo<int,3>> << "\n";
}
is there a shorter way in C++20? I know that you could do a concept for "specializes template", e.g.:
// checks if a type T specializes a given template class template_class
// note: This only works with typename template parameters.
// e.g.: specializes<std::array<int, 3>, std::array> will yield a compilation error.
// workaround: add a specialization for std::array.
namespace specializes_impl
{
template <typename T, template <typename...> typename>
struct is_specialization_of : std::false_type {};
template <typename... Args, template <typename...> typename template_class>
struct is_specialization_of<template_class<Args...>, template_class> : std::true_type {};
}
template <typename T, template <typename...> typename template_class>
inline constexpr bool is_specialization_of_v = specializes_impl::is_specialization_of<T,template_class>::value;
template <typename T, template <typename...> typename template_class>
using is_specialization_of_t = specializes_impl::is_specialization_of<T,template_class>::type;
template<typename T, template <typename...> typename template_class>
concept specializes = is_specialization_of_v<T, template_class>;
However, this fails with non-type template parameters such as "size_t". Is there a way so I could, for example just write:
void test(Foo auto xy);
I know that since C++20 there came a couple of other ways to constrain template parameters of a function, however, is there a short way to say "I dont care how it specializes the struct as long as it does"?
Nope.
There's no way to write a generic is_specialization_of that works for both templates with all type parameters and stuff like std::array (i.e. your Foo), because there's no way to write a template that takes a parameter of any kind.
There's a proposal for a language feature that would allow that (P1985), but it's still just a proposal and it won't be in C++23. But it directly allows for a solution for this. Likewise, the reflection proposal (P1240) would allow for a way to do this (except that you'd have to spell the concept Specializes<^Foo> instead of Specializes<Foo>).
Until one of these things happens, you're either writing a bespoke concept for your template, or rewriting your template to only take type parameters.
I have many EnableIf traits that basically check whether the input type satisfies an interface. I was trying to create a generic Resolve trait that can be used to transform those into a boolean trait.
Something like this - https://wandbox.org/permlink/ydEMyErOoaOa60Jx
template <
template <typename...> class Predicate,
typename T,
typename = std::void_t<>>
struct Resolve : std::false_type {};
template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, Predicate<T>> : std::true_type {};
Now if you have an EnableIf trait like so
template <typename T>
using EnableIfHasFoo = std::void_t<decltype(std::declval<T>().foo())>;
You can create a boolean version of that very quickly
template <typename T>
struct HasFoo : Resolve<EnableIfHasFoo, T> {};
Or the analogous variable template.
But for some reason the partial specialization is not working as expected. Resolve does not work as intended. See the output here - https://wandbox.org/permlink/ydEMyErOoaOa60Jx. The same thing implemented "manually" works - https://wandbox.org/permlink/fmcFT3kLSqyiBprm
I am resorting to manually defining the types myself. Is there a detail with partial specializations and template template arguments that I am missing?
I cannot find the exact reason why your example don't work. If you want to dig more into the details of std::void_t, here's an interesting explanation
Even if I cannot explain it in depth, I would like to add another reliable syntax that is used in the detection idiom.
template<
template <typename...> class Predicate,
typename T,
typename = void>
struct Resolve : std::false_type {};
template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, std::void_t<Predicate<T>>> : std::true_type {};
template <typename T>
using EnableIfHasFoo = decltype(std::declval<T>().foo());
live on compiler explorer
The reason why your approach will fail is that Predicate<T>> in the third template parameter is not a non-deduced context. This causes the deduction to directly fail (see [temp.alias]/2), instead of using the deduced template arguments from elsewhere as in a non-deduced context.
You can wrap your Predicate<T>> to a non-deduced context to make it work:
template<class T>
struct identity {
using type = T;
};
template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, typename identity<Predicate<T>>::type> : std::true_type {};
Live Demo
Because inside a non-deduced context, the deduction won't happen for Predicate<T> part, instead, it will use the Predicate and T obtained from elsewhere.
As for why the usual detection-idiom (see Guillaume Racicot's answer) will work, it is because std::void_t as a template alias, will be replaced by void in deduction phase (see [temp.alias]/2), thus no deduction will happen.
Here are some examples to illustrate it more clearly:
template<class T>
using always_int = int;
template<template<class> class TT>
struct deductor {};
template<template<class> class TT, class T>
void foo(T, deductor<TT>) {}
template<template<class> class TT, class T>
void bar(T, deductor<TT>, TT<T>) {}
template<class T>
void baz(T, always_int<T>) {}
int main() {
// ok, both T and TT are deduced
foo(0, deductor<always_int>{});
// ERROR, TT<T> is NOT a non-deduced context, deduction failure
bar(0, deductor<always_int>{}, 0);
// ok, T is deduced, always_int<T> is replaced by int so no deduction
baz(0, 0);
}
I have traits classes sprinkled about my code which follow the same basic idiom:
template<class Frame, typename = void>
struct frame_traits
{
typedef void base_frame_type;
};
template<class Frame>
struct frame_traits<Frame, typename std::void_t<
typename Frame::base_frame_type>::type>
{
typedef typename Frame::base_frame_type base_frame_type;
};
and I have a bunch of trait checkers which use them, which also follow a similar idiom:
template <typename T>
struct has_base_frame_type : std::integral_constant<bool,
!std::is_same<typename frame_traits<T>::base_frame_type, void>::value>::type {};
however, it turns out that has_base_frame_type has become useful to multiple concepts in my code, and I'd like to generalize it further so that I can pass the traits class as an additional parameter:
template <typename T, template<typename> class Traits = frame_traits>
struct has_base_frame_type : std::integral_constant<bool,
!std::is_same<typename Traits<T>::base_frame_type, void>::value>::type {};
This doesn't work though, since templates with default arguments cannot be used as template template parameters.
I know I could work around the problem if I always use a traits class in the template instantiation (and modify the trait checker to accept it), namely
has_base_frame_type<frame_traits<MyClass>>::value
but I don't want to do that, because it would be all too easy to forget and pass in a non-trait class. In fact, that's how I originally had the code written until I forgot the trait one too many times and refactored it.
Is there someway I can modify my trait class idiom to work around the template template parameter problem?
Framework:
#include <type_traits>
template <typename...>
using void_t = void;
template <typename AlwaysVoid, template <typename...> class Operation, typename... Args>
struct detect_impl : std::false_type {};
template <template <typename...> class Operation, typename... Args>
struct detect_impl<void_t<Operation<Args...>>, Operation, Args...> : std::true_type {};
template <template <typename...> class Operation, typename... Args>
using detect = detect_impl<void, Operation, Args...>;
Detectors:
template <class Frame>
using frame_traits = typename Frame::base_frame_type;
template <class Frame>
using other_frame_traits = typename Frame::other_frame_type;
Trait with a default detector:
template <typename T, template <typename...> class Traits = frame_traits>
using has_frame_type = detect<Traits, T>;
Test:
struct A
{
using base_frame_type = void;
};
struct B
{
using other_frame_type = void;
};
int main()
{
static_assert(has_frame_type<A>{}, "!"); // default
static_assert(!has_frame_type<B>{}, "!"); // default
static_assert(!has_frame_type<A, other_frame_traits>{}, "!"); // non-default
static_assert(has_frame_type<B, other_frame_traits>{}, "!"); // non-default
}
DEMO
I am wondering if it's possible to combine policy classes using variadic template-template parameters such that each policy may have it's own template pack. It seems like you can only share a single template pack amongst all policies but I hope that's not the case.
The following seems to be what's possible:
template <
class T,
template <class, typename...> class Policy1,
template <class, typename...> class Policy2,
template <class, typename...> class Policy3,
typename... Args
>
struct PolicyClass
: public Policy1 <ObjT, Args...>
, public Policy2 <ObjT, Args...>
, public Policy3 <ObjT, Args...> {}
I'm hoping each policy can have it's own pack so I could make something like this (?):
template <class T>
struct implementedPolicy1 {};
template <class T>
struct implementedPolicy2 {};
template <class T, class A>
struct implementedPolicy3 {};
PolicyClass <ObjT,
implementedPolicy1,
implementedPolicy2,
implementedPolicy3<AType>
>
The idea being each of the policies are all using the same object type but the third has some further templating. I know that's incorrect code above - just trying to illustrate what I'd like to be able to do.
Thanks
I have never been fan of template template parameters, and this is one other instance I would avoid them:
template <typename T, typename... Policies>
struct PolicyClass: Policies... {};
will just work with arbitrary policies:
using PC = PolicyClass<int,
LifetimePolicy<LP::Extended>,
DurabilityPolicy<3600, DP::Seconds>
StoragePolicy<int, SP::InMemory>>;
You need to be able to delimit between the packs.
// helper template. Using `std::tuple<>` instead is another option.
template<class...>struct type_list {};
// base, note no body:
template <
class T,
template <class, typename...> class Policy0,
template <class, typename...> class Policy1,
template <class, typename...> class Policy2,
typename... Packs
>
struct PolicyClass;
// specialization:
template <
class T,
template <class, typename...> class Policy0,
template <class, typename...> class Policy1,
template <class, typename...> class Policy2,
typename... A0s,
typename... A1s,
typename... A2s
>
struct PolicyClass<
T, Policy1, Policy2, Policy3,
type_list<A0s...>, type_list<A1s...>, type_list<A2s...>
>
: Policy0<T, A0s...>, Policy1<T, A1s...>, Policy2<T, A2s...> {}
where I pack each policies extra arguments into a type_list.
In theory you could do fancier things, like have particular "tag" types being the delimiters, but it ends up being a lot of gymnastics.
PolicyClass< int, bob, eve, alice, type_list<>, type_list<double>, type_list<char, char, char> > foo;
will create
PolicyClass: bob<int>, alice<int, double>, eve<int, char, char, char>
roughly.
Note that you may have something like:
template <class T,
template <class> class Policy1,
template <class> class Policy2,
template <class> class Policy3>
struct PolicyClass : public Policy1<ObjT>,
public Policy2<ObjT>,
public Policy3<ObjT>
{};
template <class T> struct implementedPolicy1 {};
template <class T> struct implementedPolicy2 {};
template <class T, class A> struct implementedPolicy3 {};
// Adapt the policy interface
template <class T>
using myImplementedPolicy3 = implementedPolicy3<T, AType>; // Assuming AType exist
PolicyClass <ObjT, implementedPolicy1, implementedPolicy2, myImplementedPolicy3> policies;