Check with which template parameter a class was instantiated (compile time) - c++

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!

Related

Template-template, variadic template, and deduction guide: compiler bug?

Consider the following heavily templated code:
// Preamble
#include <list>
#include <deque>
#include <vector>
#include <iostream>
#include <type_traits>
// Rebind template template type traits
template <class> struct rebind_template_template;
template <template <class...> class Template, class... Types>
struct rebind_template_template<Template<Types...>> {
template <class... Args>
using type = Template<Args...>;
};
// Rebind template parameters type traits
template <class> struct rebind_template_parameters;
template <template <class...> class Template, class... Types>
struct rebind_template_parameters<Template<Types...>> {
template <template <class...> class Arg>
using type = Arg<Types...>;
};
// Template pack
template <template <class...> class... Templates>
class template_pack
{
private:
template <class... Args>
using if_constructible_t = std::void_t<
typename rebind_template_parameters<Args>::template type<Templates>...
>;
public:
template <class... Args, class = if_constructible_t<Args...>>
constexpr template_pack(const Args&...) noexcept {}
};
// Class template argument deduction guide
template <class... Args>
template_pack(const Args&...) -> template_pack<
rebind_template_template<Args>::template type...
>;
// Pretty-printing
template <class Arg>
void print() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
// Main
int main(int argc, char* argv[])
{
template_pack pack(std::list<int>{}, std::deque<int>{}, std::vector<int>{});
print<decltype(pack)>();
return 0;
}
What this does, is that it deduces a pack of template templates from its arguments. It works on gcc 8 and 9, but not on clang. But I have no idea whether this is even valid C++. Regarless since it works on gcc but not on clang, one is right, but the other is not. Which one is right?
The error returned by clang is:
error: pack expansion contains parameter pack 'Args'
that has a different length (3 vs. 1) from outer parameter packs
Note: any simpler code that would reproduce the problem is welcome.

Pass a class template as template argument

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

Retrieve the template a type is instantiated from

How can I retrieve the template a type was originally instantiated from?
I'd like to do the following:
struct Baz{};
struct Bar{};
template <typename T>
struct Foo {};
using SomeType = Foo<Bar>;
template <typename T>
using Template = get_template<SomeType>::template type<T>;
static_assert(std::is_same<Foo<Baz>, Template<Baz>>::value, "");
I know I can achieve this through partial specialization, but this forces me to specialize get_template for every template I want to use it with:
template <typename T>
struct get_template;
template <typename T>
struct get_template<Foo<T>>
{
template <typename X>
using type = Foo<X>;
};
live example
Is there a way around this limitation?
You could do something like that, using a template template parameter (should work for templates with any number of type arguments):
template <typename T>
struct get_template;
template <template <class...> class Y, typename... Args>
struct get_template<Y<Args...>> {
template <typename... Others>
using type = Y<Others...>;
};
Then to get the template:
template <typename T>
using Template = typename get_template<SomeType>::type<T>;
As mentioned by #Yakk in the comment, the above only works for template that only have type arguments. You can specialize for template with specific pattern of type and non-type arguments, e.g.:
// Note: You need the first size_t to avoid ambiguity with the first specialization
template <template <class, size_t, size_t...> class Y, typename A, size_t... Sizes>
struct get_template<Y<A, Sizes...>> {
template <class U, size_t... OSizes>
using type = Y<U, OSizes...>;
};
...but you will not be able to specialize it for arbitrary templates.
DEMO (with Foo and std::pair):
#include <type_traits>
#include <map>
struct Bar{};
template <typename T>
struct Foo {};
using SomeType = Foo<Bar>;
template <typename T>
struct get_template;
template <template <class...> class Y, typename... Args>
struct get_template<Y<Args...>> {
template <typename... Others>
using type = Y<Others...>;
};
template <typename T>
using Template = typename get_template<SomeType>::type<T>;
static_assert(std::is_same<SomeType, Template<Bar>>::value, "");
static_assert(std::is_same<Foo<int>, Template<int>>::value, "");
using PairIntInt = std::pair<int, int>;
using PairIntDouble = std::pair<int, double>;
template <typename U1, typename U2>
using HopeItIsPair =
typename get_template<PairIntDouble>::type<U1, U2>;
static_assert(std::is_same<PairIntDouble, HopeItIsPair<int, double>>::value, "");
static_assert(std::is_same<PairIntInt, HopeItIsPair<int, int>>::value, "");
Not sure I got the question. Would this work?
#include<type_traits>
#include<utility>
template<typename V, template<typename> class T, typename U>
auto get_template(T<U>) { return T<V>{}; }
struct Baz{};
struct Bar{};
template <typename T>
struct Foo {};
using SomeType = Foo<Bar>;
template <typename T>
using Template = decltype(get_template<T>(SomeType{}));
int main() {
static_assert(std::is_same<Foo<Baz>, Template<Baz>>::value, "");
}
You can generalize even more (SomeType could be a template parameter of Template, as an example), but it gives an idea of what the way is.

Generating template specializations through template metaprogramming. Odd compiler behaviour

Source code
This is basically a non-recursive std::tuple_element implementation.
Note: To make this non-recursive, you must replace std::make_index_sequence with a non-recursive implementation. I left it with std::make_index_sequence in order to provide a MVCE.
deduct<std::size_t, T> has a specialization of deduct_impl<T> that is generated from the index sequence template argument it receives. It is used in order to deduce the type at index in a variadic type template or tuple.
itp<std::size_t> and itp<std::size_t, T> is an index-type-pair used to expand the variadic indices template with a type variadic template in order to match the generated specialization.
deducer<std::size_t, T...> puts it all together by specializing deduct<std::size_t, T> and deduct_impl<T> by using std::conditional_t to generate the correct specialization.
Basically, for std::tuple<void, int, char>, in order to get the type at index 1, it creates itp_base<0>, itp<1, int>, itp_base<2> and passes it to deduct and deduct_impl.
#include <iostream>
#include <string>
#include <tuple>
template <std::size_t index>
struct itp_base {};
template <std::size_t index, typename T>
struct itp : itp_base<index> {};
template <std::size_t index, typename IndexSequence>
struct deduct;
template <std::size_t index, std::size_t... indices>
struct deduct<index, std::index_sequence<indices...>>
{
template <typename Tuple>
struct deduct_impl;
template <typename T, typename... R>
struct deduct_impl<std::tuple<itp_base<indices>..., itp<index, T>, R...>>
{
using type = T;
};
};
template <std::size_t index, typename... Types>
class deducer
{
private:
static_assert( index < sizeof...( Types ), "deducer::index out of bounds" );
template <typename IndexSequence>
struct deducer_impl;
template <std::size_t... indices>
struct deducer_impl<std::index_sequence<indices...>>
{
using type = typename deduct<index, std::make_index_sequence<index>
>::template deduct_impl
<
std::tuple
<
std::conditional_t
<
std::is_base_of<itp_base<indices>, itp<index, Types>>::value,
itp<index, Types>,
itp_base<indices>
>...
>
>::type;
};
public:
using type = typename deducer_impl<
std::make_index_sequence<sizeof...( Types )>>::type;
};
template <std::size_t index, typename... Types>
using tuple_element_t = typename deducer<index, Types...>::type;
int main()
{
tuple_element_t<3, int, void, char, std::string> s{ "string" };
std::cout << s << '\n';
}
Odd compiler behaviour
Clang++
I'm getting warnings for non-deducible template arguments from Clang++, but the program outputs correctly.
Visual C++ v140
The type is detected correctly. However I get the following warning:
warning C4552: '<<': operator has no effect; expected operator with side-effect
The program does not output anything.
G++
Everything works properly.
Demo
http://coliru.stacked-crooked.com/a/7cb3ac06ab4b2d4c

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.