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

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.

Related

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

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!

C++ template template template parameter?

As a contrived example, let's say I want to write a type that can add a new template template parameter to a type that has a variadic template template parameter pack:
namespace
{
template <template <typename...> typename... T>
struct foo {};
template <typename Original, template <typename...> typename T>
class add_to_foo {
template <template <typename...> typename... V>
struct appender {
using type = foo<V..., T>;
explicit appender(foo<V...>) {}
};
template <template <typename...> typename... V>
appender(foo<V...>) -> appender<V...>;
public:
using type = typename decltype(appender(std::declval<Original>()))::type;
};
}
int main() {
using original_foo = foo<std::vector, std::basic_string>;
using new_foo = typename add_to_foo<original_foo, std::deque>::type;
static_assert(std::is_same_v<new_foo, foo<std::vector, std::basic_string, std::deque>>);
return EXIT_SUCCESS;
}
It works, but it's limited in that appender only works with foo rather than a generic template parameter. Let's try to replace foo with a template template parameter:
namespace
{
template <template <typename...> typename... T>
struct foo {};
template <typename Original, template <typename...> typename T>
class add_to_u {
template <template <typename...> typename U, template <typename...> typename... V>
struct appender {
using type = U<V..., T>;
explicit appender(U<V...>) {}
};
template <template <typename...> typename U, template <typename...> typename... V>
appender(U<V...>) -> appender<V...>;
public:
using type = typename decltype(appender(std::declval<Original>()))::type;
};
}
int main() {
using original_foo = foo<std::vector, std::basic_string>;
using new_foo = typename add_to_u<original_foo, std::deque>::type;
static_assert(std::is_same_v<new_foo, foo<std::vector, std::basic_string, std::deque>>);
return EXIT_SUCCESS;
}
This doesn't work, there are lot's of errors, but the main ones are:
<source>:14:25: error: use of template template parameter 'V' requires template arguments
using type = U<V..., T>;
<source>:23:36: error: no viable constructor or deduction guide for deduction of template arguments of 'appender'
using type = typename decltype(appender(std::declval<Original>()))::type;
It looks like the compiler thinks that the template arguments of U need to be complete types rather than template template parameters, this isn't the case when foo was used presumably because the compiler could see that it used template template parameters. So I need to tell the compiler that U is a template template parameter whose parameters are also template template parameters - any idea how?

Deduction guide to extract template template from types

Consider the following class:
// Class definition
template <template <class...> class... Templates>
class template_pack
{
public:
template <class... Types>
constexpr template_pack(const Types&...) noexcept;
};
// Class template argument deduction guide
template <class... Types>
template_pack(const Types&...) -> template_pack</* something here */>
We suppose that the Types... are of the form template <class...> class... Templates. What I would like is:
template_pack pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
to lead to:
template_pack<std::vector, std::list, std::deque>;
How to make that work?
How to make that work?
I don't see a way: there is ever something that can't be deduced.
Not exactly what you asked but the best I can imagine pass trough a custom type-traits ttw (for "template-template-wrapper")
template <template <typename...> class C>
struct ttw
{
template <typename ... Ts>
constexpr ttw (C<Ts...> const &)
{ }
};
that, using implicit deduction guides, extracts the template-template from the type received from constructor and use it as template parameter.
So you can write template_pack with a constructor that receives ttw<Templates>
template <template <typename...> class... Templates>
struct template_pack
{
constexpr template_pack (ttw<Templates> const & ...)
{ }
};
that you can use as follows (again: trough implicit deduction guides)
template_pack tp1 {ttw{std::vector<int>{}},
ttw{std::set<long>{}},
ttw{std::map<char, short>{}}};
The problem is that it's necessary explicitly wrap the arguments in ttw{} because, to make an example, a std::vector<int> is convertible to a ttw<std::vector> but isn't a ttw<std::vector>. So, passing std::vector{} instead of ttw{std::vector{}}, we have the usual chicken/egg problem of a type that can't be deduced because, to deduce it, is needed a conversion that require to knowledge of type that we want deduce.
Obviously, you can demand the explicit ttw wrapping works to a specific make_template_pack() function
template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
{ return template_pack{ttw{std::forward<Ts>(ts)}...}; }
The following is a full compiling example
#include <map>
#include <set>
#include <vector>
#include <type_traits>
template <template <typename...> class C>
struct ttw
{
template <typename ... Ts>
constexpr ttw (C<Ts...> const &)
{ }
};
template <template <typename...> class... Templates>
struct template_pack
{
constexpr template_pack (ttw<Templates> const & ...)
{ }
};
template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
{ return template_pack{ttw{std::forward<Ts>(ts)}...}; }
int main ()
{
template_pack tp1 {ttw{std::vector<int>{}},
ttw{std::set<long>{}},
ttw{std::map<char, short>{}}};
auto tp2 { make_template_pack(std::vector<long>{},
std::set<int>{},
std::map<char, short>{}) };
using t0 = template_pack<std::vector, std::set, std::map>;
using t1 = decltype(tp1);
using t2 = decltype(tp2);
static_assert( std::is_same<t0, t1>::value );
static_assert( std::is_same<t0, t2>::value );
}
I "succeed" with additional traits:
template <typename T> struct template_traits;
// Variadic case
template <template <class...> class C, typename ... Ts>
struct template_traits<C<Ts...>>
{
template <typename ... Us>
using template_type = C<Us...>;
};
And then, argument deduction is:
// Class template argument deduction guide
template <class... Types>
template_pack(Types&&...)
-> template_pack<template_traits<std::decay_t<Types>>::template template_type...>;
Demo
Issue is that aliases are not really identical (gcc considers some aliases as identical
contrary to clang) (I opened a question for that BTW)
template_traits<std::vector>::template_type is not std::vector even if for any T, A, template_traits<std::vector>::template_type<T, A> is not std::vector<T, A>.
There's a shortcut you can take if each template has only one argument:
template <template<class> class... Templates, class... Types>
template_pack(const Templates<Types>&...) -> template_pack<Templates...>;
With only one argument each, it's easy to split up one pack amongst all the templates.
Unfortunately, I don't know of any way to have a separate pack per template without knowing the number of templates in advance. Therefore, a layer of indirection through a helper seems required. In addition, deduction guides must be of the form -> template_pack<something>, presumably to avoid having the compiler do too much work or run into impossible problems. Given this, the class needs a slight tweak:
template <template <class...> class... Templates>
class holder {};
// Class definition
template<class Holder>
class template_pack;
template <template <class...> class... Templates>
class template_pack<holder<Templates...>>
{
public:
template <class... Types>
constexpr template_pack(const Types&...) noexcept {}
};
With this tweak, we can make a helper (that could probably be simplified to be a bit more straightforward):
template<template<class...> class... TTs>
struct result {
using type = holder<TTs...>;
};
template<class T>
struct type {};
template<class Prev, class Current, class... Rest>
auto helper() {
return []<template<class...> class... PrevTTs, template<class...> class CurrTT, class... CurrTs>(result<PrevTTs...>, type<CurrTT<CurrTs...>>) {
if constexpr (sizeof...(Rest) == 0) {
return result<PrevTTs..., CurrTT>{};
} else {
return helper<result<PrevTTs..., CurrTT>, Rest...>();
}
}(Prev{}, type<Current>{});
}
I use C++20's templated lambdas to pull apart two templates from their arg packs inline instead of having an additional helper layer, but that additional layer is still possible in earlier standards, just uglier. The helper recursively takes a previous result, pulls apart one template at a time, adds it to the result, and recursively calls itself until there are no arguments left.
With this helper, it becomes possible to make the deduction guide:
// Class template argument deduction guide
template <typename... Ts>
template_pack(const Ts&...) -> template_pack<typename decltype(helper<result<>, Ts...>())::type>;
You can find a full example here. It might also be possible to improve this code somewhat significantly, but the core idea is there.
Something like that seems to be working
#include <iostream>
#include <vector>
#include <list>
#include <deque>
template<typename... TS>
struct Pack;
template<typename S, typename... TS>
struct Pack<S, TS...> {
S s;
Pack<TS...> ts;
static constexpr size_t size = Pack<TS...>::size + 1;
constexpr Pack(S&& s, TS&&... ts) noexcept
: s(s)
, ts(std::forward<TS>(ts)...)
{}
};
template<typename S>
struct Pack<S> {
S s;
static constexpr size_t size = 1;
constexpr Pack(S&& s) noexcept
: s(s)
{}
};
template<>
struct Pack<> {
static constexpr size_t size = 0;
};
template<typename... TS>
constexpr auto make_pack(TS&&... ts) noexcept {
return Pack<TS...>(std::forward<TS>(ts)...);
}
int main() {
auto empty_pack = make_pack();
std::cout << empty_pack.size << std::endl; // 0
auto vector_pack = make_pack(std::vector<int>{});
std::cout << vector_pack.size << std::endl; // 1
auto vector_list_deque_pack = make_pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
std::cout << vector_list_deque_pack.size << std::endl; // 3
}

Error with MSVC only: use of class template requires template argument list

This compiles fine without errors on both clang and gcc, but fails with MSVC: (compiler explorer)
// detection idiom
template <class...>
using void_t = void;
template <class Void, template <class...> class Op, class... Args>
struct detector_base
{
using type = void;
};
template <template <class...> class Op, class... Args>
struct detector_base<void_t<Op<Args...>>, Op, Args...>
{
using type = Op<Args...>;
};
template <class T>
using check = decltype(T::x);
template <typename T>
struct type {};
using dt = typename detector_base<void, check, int>::type; // this is void
using t = type<dt>;
// ^--- 't': use of class template requires template argument list
This occurs when using the detected type with any template type. Is this a compiler bug, or is due to lack of support (if so, for what)?

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.