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
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.
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
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.
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
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.