Generating template specializations through template metaprogramming. Odd compiler behaviour - c++

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

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!

How to get an element of type list by index

How can an element of a type list using L = type_list<T1, T2, ...> be retrieved by index, like std::tuple_element, preferrably in a non recursive way?
I want to avoid using tuples as type lists for use cases, that require instantiation for passing a list like f(L{}).
template<typename...> struct type_list {};
using L = typelist<int, char, float, double>;
using T = typeAt<2, L>; // possible use case
Not sure if an iteration using std::index_sequence and a test via std::is_same of the std::integral_constant version of the index is a good aproach.
I want to avoid using tuples as type lists for use cases, that require
instantiation for passing a list like f(L{})
If you don't want to instanciate std::tuple but you're ok with it in
unevaluated contexts, you may take advantage of std::tuple_element to
implement your typeAt trait:
template <std::size_t I, typename T>
struct typeAt;
template <std::size_t I, typename... Args>
struct typeAt<I, type_list<Args...>> : std::tuple_element<I, std::tuple<Args...>> {};
// ^ let library authors do the work for you
using L = type_list<int, char, float, double>;
using T = typename typeAt<2, L>::type;
static_assert(std::is_same<T, float>::value, "");
Using Boost.Mp11, this is a one-liner (as always):
template<typename...> struct type_list {};
using L = typelist<int, char, float, double>;
using T = mp_at_c<L, 2>; // <==
Note that this will compile significantly more efficiently than using tuple_element. On clang, the implementation even uses a compiler intrinsic.
You might re-implement a simplified non-recursive tuple-like version if you don't want to use std::tuple:
template <std::size_t I, typename T>
struct type_list_leaf
{
using type = T;
};
template <typename T> struct tag{ using type = T; };
template <typename Seq, typename...>
struct type_list_impl;
template <std::size_t... Is, typename... Ts>
struct type_list_impl<std::index_sequence<Is...>, Ts...> : type_list_leaf<Is, Ts>...
{
};
template <std::size_t I, typename T>
tag<T> type_list_element_tag(const type_list_leaf<I, T>&);
template <std::size_t I, typename Tuple>
using tuple_element = decltype(type_list_element_tag<I>(std::declval<Tuple>()));
template <std::size_t I, typename Tuple>
using tuple_element_t = typename tuple_element<I, Tuple>::type;
template <typename ... Ts>
using type_list = type_list_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...>;
Demo

Subset a Variadic template given a constexpr boolean selection function

Suppose we have a variadic templated class like
template<class...Ts>
class X{
template<size_t I>
constexpr bool shouldSelect();
std::tuple<TransformedTs...> mResults; // this is want I want eventually
};
where the implementation of shouldSelect is not provided, but what it does is that, given an index i referring to the ith element of the variadic Ts, tells you whether we should select it to the subset.
I want to do a transformation on Ts such that only classes Ts at indexes that results in shouldSelect returning true should be selected. Is there an easy way to do this?
For example, if shouldSelect returns true for I = 1,2,4, and Ts... = short, int, double, T1, T2, then I want to get a TransformedTs... that is made up of int, double, T2. Then I can use this TransformedTs... in the same class.
If you're able to use C++17, this is pretty easy to implement using a combination of if constexpr and expression folding.
Start with some helper types, one with parameters to track the arguments to X::shouldSelect<I>(), and the other with a type to test.
template <typename T, size_t I, typename...Ts>
struct Accumulator {
using Type = std::tuple<Ts...>;
};
template <typename T>
struct Next { };
Then an operator overload either adds the type to the accumulator, or not with if constexpr:
template <typename TAcc, size_t I, typename... Ts, typename TArg>
decltype(auto) operator +(Accumulator<TAcc, I, Ts...>, Next<TArg>) {
if constexpr (TAcc::template shouldSelect<I>()) {
return Accumulator<TAcc, I + 1, Ts..., TArg>{};
} else {
return Accumulator<TAcc, I + 1, Ts...>{};
}
}
Finally, you can put it all together with a fold expression and extract the type with decltype:
template <template <typename... Ts> class T, typename... Ts>
constexpr decltype(auto) FilterImpl(const T<Ts...>&) {
return (Accumulator<T<Ts...>, 0>{} + ... + Next<Ts>{});
}
template<typename T>
using FilterT = typename decltype(FilterImpl(std::declval<T>()))::Type;
Usage:
using Result = FilterT<X<int, double, bool, etc>>;
Demo: https://godbolt.org/z/9h89zG
If you don't have C++17 available to you, it's still possible. You can do the same sort of conditional type transfer using a recursive inheritance chain to iterate though each type in the parameter pack, and std::enable_if to do the conditional copy. Below is the same code, but working in C++11:
// Dummy type for copying parameter packs
template <typename... Ts>
struct Mule {};
/* Filter implementation */
template <typename T, typename Input, typename Output, size_t I, typename = void>
struct FilterImpl;
template <typename T, typename THead, typename... TTail, typename... OutputTs, size_t I>
struct FilterImpl<T, Mule<THead, TTail...>, Mule<OutputTs...>, I, typename std::enable_if<( T::template shouldSelect<I>() )>::type >
: FilterImpl<T, Mule<TTail...>, Mule<OutputTs..., THead>, (I + 1)>
{ };
template <typename T, typename THead, typename... TTail, typename... OutputTs, size_t I>
struct FilterImpl<T, Mule<THead, TTail...>, Mule<OutputTs...>, I, typename std::enable_if<( !T::template shouldSelect<I>() )>::type >
: FilterImpl<T, Mule<TTail...>, Mule<OutputTs...>, (I + 1)>
{ };
template <typename T, typename... OutputTs, size_t I>
struct FilterImpl<T, Mule<>, Mule<OutputTs...>, I>
{
using Type = std::tuple<OutputTs...>;
};
/* Helper types */
template <typename T>
struct Filter;
template <template <typename... Ts> class T, typename... Ts>
struct Filter<T<Ts...>> : FilterImpl<T<Ts...>, Mule<Ts...>, Mule<>, 0>
{ };
template <typename T>
using FilterT = typename Filter<T>::Type;
Demo: https://godbolt.org/z/esso4M

Template template parameter simple example

First I was learning about template template parameters, and I started wondering if I had a vector<vector<int>>, if I could make a template that extracts out the type int from there.
But, in the process of trying to build an example, I can't even get a single-level template parameter template function to work!
#include <iostream>
#include <vector>
template<
template<class> class C2,
class I
>
void for_2d(const C2<I>& container)
{
for( auto j : container ){
std::cout << j;
}
}
int main() {
std::vector<int> cont;
for_2d(cont);
return 0;
}
This produces:
17 : <source>:17:5: error: no matching function for call to 'for_2d'
for_2d(cont);
^~~~~~
8 : <source>:8:6: note: candidate template ignored: substitution failure : template template argument has different template parameters than its corresponding template template parameter
void for_2d(const C2<I>& container)
^
1 error generated.
Compiler exited with result code 1
The thing you are missing is that vector has multiple template arguments (most of them has default value).
You need to prepare your function for this
template<
template<class...> class C2,
class I
>
void for_2d(const C2<I>& container)
{
for( auto j : container ){
std::cout << j;
}
}
Notice the dots after class
+1 for the Bartosz Przybylski's answer, that explain why your example doesn't compile, but you want
extracts out the type int from there
You use auto j : container, so you're using (at least) C++11; so I suggest you the implementation of a specific, and recursive, type traits.
I propose the following firtType
First of all, the generic (not specialized) version (that is the recursion terminal)
template <typename T>
struct firstType
{ using type = T; };
Next the specialization for std::vector and other containers similar container (that receiving a seguence of types)
template <template <typename...> class C, typename T0, typename ... Ts>
struct firstType<C<T0, Ts...>>
{ using type = typename firstType<T0>::type; };
This specialization works with a lot of containers but not with std::array, that receive a type and a number; the following is a specialization for std::array
template <template <typename, std::size_t> class C, typename T, std::size_t N>
struct firstType<C<T, N>>
{ using type = typename firstType<T>::type; };
Other specializations may be required.
The following is a full working example
#include <array>
#include <vector>
#include <type_traits>
template <typename T>
struct firstType
{ using type = T; };
template <template <typename...> class C, typename T0, typename ... Ts>
struct firstType<C<T0, Ts...>>
{ using type = typename firstType<T0>::type; };
template <template <typename, std::size_t> class C, typename T, std::size_t N>
struct firstType<C<T, N>>
{ using type = typename firstType<T>::type; };
int main ()
{
std::vector<int> vi;
std::array<long, 42U> al;
std::vector<std::vector<short>> vvs;
static_assert( std::is_same<typename firstType<decltype(vi)>::type,
int>::value, "!" );
static_assert( std::is_same<typename firstType<decltype(al)>::type,
long>::value, "!" );
static_assert( std::is_same<typename firstType<decltype(vvs)>::type,
short>::value, "!" );
}

Attempt to remove last type from a tuple is failing

I'm trying to remove the last element of a tuple. It works when I have only one element in the tuple to remove. But when I have more than one, things go wrong. I can't get why this isn't working. These are the errors I'm getting:
prog.cpp: In function ‘int main()’:
prog.cpp:24:22: error: incomplete type ‘remove_last<std::tuple<int, int> >’ used in nested name specifier
prog.cpp:24:22: error: incomplete type ‘remove_last<std::tuple<int, int> >’ used in nested name specifier
prog.cpp:24:70: error: template argument 1 is invalid
#include <tuple>
#include <type_traits>
template <class T>
struct remove_last;
template <class T>
struct remove_last<std::tuple<T>>
{
using type = std::tuple<>;
};
template <class... Args, typename T>
struct remove_last<std::tuple<Args..., T>>
{
using type = std::tuple<Args...>;
};
int main()
{
std::tuple<int, int> var;
static_assert(
std::is_same<remove_last<decltype(var)>::type,
std::tuple<int>>::value, "Values are not the same"
);
}
The errors go away when I make the template argument non-variadic in one of the specializations. But then that becomes a specialization which will only process a tuple with two elements - not what I was aiming for. How can I get this to work with variadic arguments? In other words, how can I get this to work when there is more than one element in the tuple?
The problem is that the argument pack is greedy and - since it comes first - eats up all the types in the sequence when performing type deduction, including the T you expect to be left out of Args....
You could define the variadic specialization this way (notice that the argument pack is now appearing last in std::tuple<T, Args...>):
template <class T, class... Args>
struct remove_last<std::tuple<T, Args...>>
{
using type = typename concat_tuple<
std::tuple<T>,
typename remove_last<std::tuple<Args...>>::type
>::type;
};
And have the concat_tuple meta-function defined this way:
template<typename, typename>
struct concat_tuple { };
template<typename... Ts, typename... Us>
struct concat_tuple<std::tuple<Ts...>, std::tuple<Us...>>
{
using type = std::tuple<Ts..., Us...>;
};
An alternative solution, requires C++14 or newer:
#include <tuple>
template<class Tuple>
struct remove_last;
template<>
struct remove_last<std::tuple<>>; // Define as you wish or leave undefined
template<class... Args>
struct remove_last<std::tuple<Args...>>
{
private:
using Tuple = std::tuple<Args...>;
template<std::size_t... n>
static std::tuple<std::tuple_element_t<n, Tuple>...>
extract(std::index_sequence<n...>);
public:
using type = decltype(extract(std::make_index_sequence<sizeof...(Args) - 1>()));
};
template<class Tuple>
using remove_last_t = typename remove_last<Tuple>::type;
See https://en.cppreference.com/w/cpp/utility/integer_sequence