I am trying to build a compile-time generated array of a certain type, based on a template parameter pack of some types. The array generation works just fine if the types are used directly. However, I'd like to apply a tag on each each and have them be interleaved in the generated array.
So, this is where I am at:
template <typename Head, typename... Tail>
static constexpr attributes_t make_attrs = {...};
and instantiated by (size is calculated elsewhere)
attributes_t attributes[sizeof...(Ts)] = {make_attrs<Ts>...};
What I would really like is to be able to make the latter tag each type in Ts, say with First and Second tags, such that I can produce a type-list like this
NewTs = First<Ts[0]>, Second<Ts[0]>, First<Ts[1]>, Second<Ts[1]>, First<Ts[2]>, Second<Ts[2]>, ...
It's important that the First and Second taggers are interleaved like that. The intent is to be able to control the instantiation of each one by its tagged version.
In my naivity I tried something like this
attributes_t attributes[2 * sizeof...(Ts)] = {make_attrs<First<Ts>, Second<Ts>>...};
in the hopes that it would expand the pack in the interleaved pattern. Unfortunately that didn't work - I am hoping that there exists a similarly simple and concise solution :)
There must be a way to do this, I'm sure. I have looked through various posts here and there and cannot find a solution that has worked yet, or perhaps I just didn't quite understand it.
I hope you have a great solution :)
As usual std::index_sequence might help:
template <typename T> struct First{};
template <typename T> struct Second{};
template <typename... Ts>
struct Types
{
};
template <typename Seq, typename Tuple>
struct Impl;
template <std::size_t... Is, typename Tuple>
struct Impl<std::index_sequence<Is...>, Tuple>
{
using type =
Types<std::conditional_t<Is % 2 == 0,
First<std::tuple_element_t<Is / 2, Tuple>>,
Second<std::tuple_element_t<Is / 2, Tuple>>>...>;
};
template <typename... Ts>
using makeRes = typename Impl<std::make_index_sequence<2 * sizeof...(Ts)>,
std::tuple<Ts...>>::type;
static_assert(std::is_same_v<Types<First<int>, Second<int>,
First<char>, Second<char>>,
makeRes<int, char>>);
Demo
if attributes_t is default constructible, assigning might be shorter:
template <typename... Ts>
constexpr std::array<attributes_t, 2 * sizeof...(Ts)> make_attributes()
{
std::array<attributes_t, 2 * sizeof...(Ts)> res{};
std::size_t index = 0;
((attributes[index++] = make_attrs<First<Ts>>,
attributes[index++] = make_attrs<Second<Ts>>),
...);
return res;
}
Folding expression is C++17, you can workaround that in C++11/C++14 with initializer_list trick:
template <typename... Ts>
constexpr std::array<attributes_t, 2 * sizeof...(Ts)> make_attributes()
{
std::array<attributes_t, 2 * sizeof...(Ts)> res{};
std::size_t index = 0;
const int dummy[] = {0, (
attributes[index++] = make_attrs<First<Ts>>,
attributes[index++] = make_attrs<Second<Ts>>,
0)...};
static_cast<void>(dummy); // silent warning for unused variable
return res;
}
Related
How can an implementation look like, that wraps around e.g. a std::tuple as a static list of type/value, plus a type (not contained in tuple) to refer to some kind of owner/visitor.
I want to instantiate like
constexpr auto data = data_of<a>(1, 2.0);
Background
The idea is to use a data_of<T>(...) kind of structure to to pass a list of type/data pairs into a bulk function like this.
template <typename... _Ts>
static constexpr void do_bulk_stuff(_Ts&&... _Vs)
{
// crazy stuff happens here
}
// call it like
do_bulk_stuff
(
data_of<a>(1, 2.0),
data_of<b>(3),
data_of<c>(4.0, 5, 6),
// ...
);
Attempt 1
So far I ended up with a naive (not working) implementation attempt like
template <typename T, typename... Ts>
struct data_of {
using type = T;
using data_t = std::tuple<Ts...>;
data_t data;
constexpr data_of(Ts&&... Vs)
: data(Vs...)
{}
};
Goal
My goal is to achieve a result like this data_of<a> instance pseudo code example
{
// meta
type = a;
data_t = std::tuple<int,double>;
// runtime
data = [1, 2.0];
}
The issue inherent to your attempt is that class template argument deduction is simply not like its function counterpart. There will be no deduction if any of the class template's arguments is explicitly specified. You fail because the trailing pack is always specified (by omission) as empty.
The solution is to shift the burden onto the mechanism that allows you to specify only part of the arguments: function templates.
template <typename T, typename... Ts>
struct data_of_t {
using type = T;
using data_t = std::tuple<Ts...>;
data_t data;
constexpr data_of_t(Ts&&... vs)
: data(std::forward<Ts>(vs)...)
{}
};
template<typename T, typename... Ts>
constexpr auto data_of(Ts&&... vs) {
return data_of_t<T, Ts...>(std::forward<Ts>(vs)...);
}
Now the type of the expression data_of<a>(1, 2.0) has the same "meta" properties you are after.
Suppose that we create two type_of functions that return std::type_identity, like:
template<auto VAR>
auto type_of() {
return std::type_identity<decltype(VAR)>{};
}
template<typename T>
auto type_of() {
return std::type_identity<T>{};
}
The way to get an actual type from std::type_identity seems a bit cumbersome:
// this works
// both i1 and i2 are ints
decltype(type_of<int>())::type i1;
decltype(type_of<int{}>())::type i2;
Is there a way to waive the need for decltype in above expressions, or to put it inside a reusable expression, to achieve something nicer like:
// can this work?
type_of<int>()::type i1;
type_of<int{}>()::type i2;
Or even better:
// can this work??
type_of_t<int> i1;
type_of_t<int{}> i2;
Note: specialization for type and non-type template parameter, which could have been a direction, doesn't work (cannot compile):
template<auto>
struct type_of;
template<typename T>
struct type_of<T> { // <== compilation error: type/value mismatch
using type = T;
};
template<auto VAR>
struct type_of<VAR> {
using type = decltype(VAR);
};
You can create a type alias. However you can't "overload" it. So my solution is to create two:
template <auto Var>
using type_of_var_t = decltype(type_of<Var>())::type;
template <class T>
using type_of_t = decltype(type_of<T>())::type;
auto test()
{
type_of_var_t<11> i1 = 24;
type_of_t<int> i2 = 17;
}
In C++, a template parameter must be a value, a type, or another template (which itself must fit within the declared template header). It must be exactly one of these.
If you want to do this:
the idea is to get something that is unaware on the caller side whether the template parameter is a type or a variable
The basic requirement for being able to do this is to write a template with a parameter that could be a value or a type.
That's not a thing C++ allows.
Template function overloading allows you to get away with something like that. But that only works because it isn't one template. It's two templates that are overloaded. Which one gets selected depends on the template arguments provided.
Template classes can't be overloaded. And template specialization cannot change the nature of the original template (like what its template parameters are). It can only allow you to reinterpret the template parameters of the original template parameters in order to provide an alternative implementation.
If you want this, you're going to have to wait until either C++ gets the ability to have a template parameter that could be anything or until C++ gets the ability to convert types into values and back (ie: reflection).
Getting the type from an std::type_identity object can be encapsulated into the following expresion:
template<auto x>
using type = typename decltype(x)::type;
This would allow to replace the cumbersome expressions:
decltype(type_of<int>())::type i1;
decltype(type_of<int{}>())::type i2;
With a more simple expression:
type<type_of<int>()> i1;
type<type_of<int{}>()> i2;
It still requires to go through two steps (type_of then type) as the first one shall be able to get a type or a variable, which is applicable only with function template overloading, then the function cannot return a type, so it returns an object that needs a template expression to extract the inner type.
Depending on what you want to do with the type, the code can become even simpler.
If all you want is to create an object of that type, you can forward the creation of the object into the function:
template<auto VAR, typename... Args>
auto create_type_of(Args&&... args) {
return decltype(VAR){std::forward<Args>(args)...};
}
template<typename T, typename... Args>
auto create_type_of(Args&&... args) {
return T{std::forward<Args>(args)...};
}
auto i1 = create_type_of<int>(7);
auto i2 = create_type_of<int{}>(7);
The generic case of creating a type from std::type_identity can work this way:
template<auto VAR>
constexpr auto type_of() {
return std::type_identity<decltype(VAR)>{};
}
template<typename T>
constexpr auto type_of() {
return std::type_identity<T>{};
}
template<typename T, typename... Args>
auto create(std::type_identity<T>, Args&&... args) {
return T{std::forward<Args>(args)...};
}
auto i1 = create(type_of<int>(), 7);
auto i2 = create(type_of<int{}>(), 7);
Note that the entire thing works only with variables that can be used as non-type template parameters. For a more generic approach that works without templates (but with a macro...), and thus can work for variables that cannot be template parameters, see this mind blowing neat answer!
By design, template arguments in C++ templates are either templates, types or values.
Within the template, you know which they are. All expressions within the template that are dependent on the template arguments are disambiguated using typename or template keywords (or context) so their category is known before arguments are substituted.
Now there are metaprogramming libraries that work with value-substitutes for all 3.
template<class T> struct type_tag_t {using type=T;};
template<class T> constexpr type_tag_t<T> tag={};
template<template<class...>class Z> struct template_z_t {
template<class...Ts>
using result = Z<Ts...>;
template<class...Ts>
constexpr result<Ts...> operator()( type_tag_t<Ts>... ) const { return {}; }
};
template<template<class...>class Z>
constexpr template_z_t<Z> template_z = {};
here templates are mapped to constexpr function objects. The template_z template lets you map type-templates over to this domain.
template<auto x>
using type = typename decltype(x)::type;
template<auto x>
constexpr std::integral_constant<std::decay_t<decltype(x)>, x> k = {};
So,
constexpr auto vector_z = template_z<std::vector>;
constexpr auto vector_tag = vector_z( tag<int>, tag<std::allocator<int>> );
then you can go back to the type:
type<vector_tag> v{1,2,3,4};
this probably isn't what you want.
You might be willing to check the proposal for Universal Template Parameters
The implication example quite match your use-case of specializing for both TTP and NTTP :
template <template auto>
struct X;
template <typename T>
struct X<T> {
// T is a type
using type = T;
};
template <auto val>
struct X<val> : std::integral_constant<decltype(val), val> {
// val is an NTTP
};
I'd like to use partial template specialization in order to 'break down' an array (which is created at compile time) into a parameter pack composed of its values (to interface with other structures I define in my code). The following (my first attempt) is not compiling
#include <array>
template <typename T, auto k> struct K;
template <typename T, std::size_t... A> struct K<T, std::array<std::size_t, sizeof...(A)>{A...}> {};
because the template argument std::array<long unsigned int, sizeof... (A)>{A ...} must not involve template parameters. As I understood it, it is not possible to provide non type parameters in a partial template specialization if they non-trivially depend on template parameters. Hence I attempted to work around this issue by containing the value in a type:
#include <array>
template <auto f> struct any_type;
template <typename T, typename array_wrapper> struct FromArr;
template <typename T, std::size_t... A>
struct FromArr<T, any_type<std::array<std::size_t, sizeof...(A)>{A...}>> {};
int main() {
FromArr<int, any_type<std::array<std::size_t, 2>{1, 2}>> d;
(void) d;
}
However, here, the partial template specialization fails when I'm trying to use it; the definition above does not match the way I use it, and I am unsure why. It fails with the following error:
file.cc: In function ‘int main()’:
file.cc:10:55: error: aggregate ‘FromArr<int, Any<std::array<long unsigned int, 2>{std::__array_traits<long unsigned int, 2>::_Type{1, 2}}> > d’ has incomplete type and cannot be defined
10 | FromArr<int, Any<std::array<std::size_t, 2>{1, 2}>> d;
Is it possible to work around this / use a different approach in order to interface the array as parameter pack?
Used Compiler
I use g++-10.0 (GCC) 10.0.1 20200124 (experimental) and compile via g++ -std=c++2a file.cc, c++2a is required because I use non-type template parameters.
Edit:
Description how the array is ment to be processed
In my real code I have got a structure which depends on -- among others -- a parameter pack (1). It would be nice if I were able to use an array (2) (which I have got in another piece of my code as a non-type template argument) to interface with that structure, as sketched in the code below.
template <int... s> struct toBeUsed; // (1)
template <std::size_t s, std::array<int, s> arr> struct Consumer { // (2)
toBeUsed<arr> instance; // This is what I would like to do
}
My Attempt is to write a helper struct as above FromStruct, which I can instantiate with an array in which I have a typedef FromStruct::type that provides toBeUsed with the correct arguments, similar to this example, which does what I want to do here with the types a std::tuple is composed of.
Link to the example
here I link the simplified usage example (2nd code block).
Inspired by #dfri 's answer, I transformed her / his solution to a version which can omit functions, but instead uses only one struct using partial template specialization for the std::integer_sequence which might also be interesting to others:
template <auto arr, template <typename X, X...> typename Consumer,
typename IS = decltype(std::make_index_sequence<arr.size()>())> struct Generator;
template <auto arr, template <typename X, X...> typename Consumer, std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...>> {
using type = Consumer<typename decltype(arr)::value_type, arr[I]...>;
};
Full example with usage:
#include <array>
/// Structure which wants to consume the array via a parameter pack.
template <typename StructuralType, StructuralType... s> struct ConsumerStruct {
constexpr auto operator()() const { return std::array{s...}; }
};
/// Solution
template <auto arr, template <typename X, X...> typename Consumer,
typename IS = decltype(std::make_index_sequence<arr.size()>())> struct Generator;
template <auto arr, template <typename X, X...> typename Consumer, std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...>> {
using type = Consumer<typename decltype(arr)::value_type, arr[I]...>;
};
/// Helper typename
template <auto arr, template <typename T, T...> typename Consumer>
using Generator_t = typename Generator<arr, Consumer>::type;
// Usage
int main() {
constexpr auto tup = std::array<int, 3>{{1, 5, 42}};
constexpr Generator_t<tup, ConsumerStruct> tt;
static_assert(tt() == tup);
return 0;
}
A C++20 approach
See OP's own answer or, for possibly instructive but more verbose (and less useful) approach, revision 2 of this answer.
A C++17 approach
(This answer originally contained an approach using a minor C++20 feature (that a lambda without any captures may be default constructed), but inspired by the original answer the OP provided a much neater C++20 approach making use of the fact that a constexpr std::array falls under the kind of literal class that may be passed as a non-type template parameter in C++20 (given restraints on its ::value_type), combined with using partial specialization over the index sequence used to unpack the array into a parameter pack. This original answer, however, made use of a technique of wrapping std::array into a constexpr lambda (>=C++17) which acted as a constexpr (specific) std::array creator instead of an actual constexpr std::array. For details regarding this approach, see revision 2 of this answer)
Following OP's neat approach, below follows an adaption of it for C++17, using a non-type lvalue reference template parameter to provide, at compile time, a reference to the array to the array to struct target.
#include <array>
#include <cstdlib>
#include <tuple>
#include <type_traits>
#include <utility>
// Parameter pack structure (concrete target for generator below).
template <typename StructuralType, StructuralType... s>
struct ConsumerStruct
{
// Use tuple equality testing for testing correctness.
constexpr auto operator()() const { return std::tuple{s...}; }
};
// Generator: FROM std::array TO Consumer.
template <const auto& arr,
template <typename T, T...> typename Consumer,
typename Indices = std::make_index_sequence<arr.size()> >
struct Generator;
template <const auto& arr,
template <typename T, T...> typename Consumer,
std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...> >
{
using type =
Consumer<typename std::remove_cv<typename std::remove_reference<
decltype(arr)>::type>::type::value_type,
arr[I]...>;
};
// Helper.
template <const auto& arr, template <typename T, T...> typename Consumer>
using Generator_t = typename Generator<arr, Consumer>::type;
// Example usage.
int main()
{
// As we want to use the address of the constexpr std::array at compile
// time, it needs to have static storage duration.
static constexpr std::array<int, 3> arr{{1, 5, 42}};
constexpr Generator_t<arr, ConsumerStruct> cs;
static_assert(cs() == std::tuple{1, 5, 42});
return 0;
}
Note that this approach places a restriction on the std::array instance in that it needs to have static storage duration. If one wants to avoid this, using a constexpr lambda which generates the array may be used as an alternative.
In C++, it's possible to use a type as a template parameter, e.g:
template <typename T>
void MyFn();
It's also possible to use a non-type as a template parameter in some cases, e.g.:
template <int64_t T>
void MyFn2();
My question is whether it's possible to have a "generic" template parameter that can be both? Like:
template <TypenameOrint64_t T>
void MyFn3();
such that both MyFn3<42> and MyFn3<double> would be acceptable.
An example of how I might use this:
template <typename ValType, ValType Head, ValType ...Tail>
struct ListS{
template <typename OutType, template <ValType ArgType> class Fn>
using MapHead = ListS<OutType, Fn<Head>::val, Tail...>;
};
template<int64_t N>
struct SquareS{
static constexpr const int64_t val = N * N;
};
using Sqrd = ListS<int64_t, 3, 4>::MapHead<int64_t, SquareS>;
static_assert(std::is_same<Sqrd, ListS<int64_t, 9, 4>>::value, "Values don't match");
The above is a very rough sketch of a compile-time list of values along with a single compile-time "function" on it. Would it be possible to make something like that also support lists of types, not just lists of non-type template param compatible values, without just duplicating all the code?
Is it possible to have a “generic” template parameter in C++, that can be either a non-type template parameter or a type?
Short answer: no.
Long answer.
No. The best I can imagine to mix types and values is wrap values in types, using std::integral_constant, by example.
So, your desired code, could be written (C++17) almost as follows
#include <utility>
template <typename ...>
struct ListS;
template <typename ValType, ValType Head, ValType ...Tail>
struct ListS<std::integral_constant<ValType, Head>,
std::integral_constant<ValType, Tail>...>
{
template <template <auto> class Fn, typename OutType = ValType>
using MapHead = ListS<std::integral_constant<OutType, Fn<Head>::value>,
std::integral_constant<OutType, Tail>...>;
};
template <auto N>
struct SquareS : public std::integral_constant<decltype(N), N*N>
{ };
int main ()
{
using T1 = ListS<std::integral_constant<long long, 3ll>,
std::integral_constant<long long, 4ll>>;
using T2 = T1::MapHead<SquareS>;
using T3 = ListS<std::integral_constant<long long, 9ll>,
std::integral_constant<long long, 4ll>>;
static_assert( std::is_same_v<T2, T3> );
}
Pre C++17 you can't use auto for the type of the template values so you should make some simple corrections.
You could use function overloading and the auto type deduction that came with C++17
to accomplish something similar.
template<typename myType>
auto myFn3(myType value){
return value;
}
template<auto value> //takes any non-type parameter
auto myFn3(){
return value;
}
int main(){
auto test1_normal = myFn3(3);
auto test1_cast = myFn3<double>(3); //able to perform a cast
auto test1_auto = myFn3<3>();
return 0;
}
So, my motivation here is to determine whether the same named type declaration within several classes are the same type. In this example, I'm looking to see that all of Foo, Bar, and Baz have an internal type Q.
#include <type_traits>
template <typename N,typename ...Ns>
using equal_type_t = typename std::enable_if_t<(std::is_same_v<N, Ns> && ...), N>;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;
int main()
{
struct Qness{};
struct Foo{using Q = Qness;};
struct Bar{using Q = Qness;};
struct Baz{using Q = Qness;};
using F = EqualQ_t<Foo,Bar,Baz>;
static_assert(std::is_same_v<F,Qness>);
return 0;
}
Tested in clang9 (praise be to godbolt).
The error reported is:
#1 with x86-64 clang 9.0.0
<source>:10:31: error: pack expansion used as argument for non-pack parameter of alias template
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;
I could probably solve this by way of doing some template recursion, but I'm trying to learn to use parameter pack expansion wherever possible.
Is this possible? Is this not an allowed context? If I separate out a few individual N types, it works fine:
template <typename N1,typename N2, typename N3, typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<N1>,ExtractQ_t<N2>,ExtractQ_t<N3>>;
I have to be having a pre-coffee brain-fog and can't see where I might be hosing the syntax.
Is there an expansion variant of this that will work?
The error diagnostic tries to say that the first parameter of equal_type_t cannot be a pack, yet you are expanding a pack into it. Thus, the simple fix is to do the same thing you did earlier:
template <typename N, typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<N>, ExtractQ_t<Ns>...>;
https://godbolt.org/z/j6_HGU
The unpacking into a non-pack + pack would require template argument deduction, but that doesn't happen for alias templates, see cppreference. You would need a struct template specialization (or template function call) to get deduction.
Using SFINAE seems a little weird in this case though. If the condition is not fulfilled, you get some compiler gibberish about SFINAE thrown in your face. There are other ways to cause a hard error during compilation.
I would say the following is the idiomatic way to write the same code, which gives you a good error when there is a problem and would (not exactly coincidentally) avoid your original problem:
template <typename ...Ns>
struct equal_type;
template <typename N,typename ...Ns>
struct equal_type<N, Ns...>
{
static_assert((std::is_same_v<N, Ns> && ...), "These types must be the same!");
using type = N;
};
template <typename ...Ns>
using equal_type_t = typename equal_type<Ns...>::type;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;
https://godbolt.org/z/u52mUE
For completeness, the pre-C++17 way (before fold expressions existed) does indeed use recursion:
template <typename N1, typename N2, typename ...Ns>
struct equal_type
{
static_assert(std::is_same_v<N1, N2>, "These types must be the same!");
using type = typename equal_type<N1, Ns...>::type;
};
template <typename N1, typename N2>
struct equal_type<N1, N2>
{
static_assert(std::is_same_v<N1, N2>, "These types must be the same!");
using type = N1;
};
https://godbolt.org/z/NKmMZD
Figured this was best suited to a self-answer, but this is mostly directed at Max, who asked a return question, and this response 'is too long to fit in the margins'.
If I'd never tried parameter packs, I probably wouldn't have gotten to this variation on template recursion to solve my problem, but it's probably where I might have gone to if I hadn't been educated on the real issue.
#include <type_traits>
template <typename N,typename ...Ns>
using equal_type_t = typename std::enable_if_t<(std::is_same_v<N, Ns> && ...), N>;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename N,typename ...Ns>
class EqualQ
{
public:
using type = equal_type_t<ExtractQ_t<N>,typename EqualQ<Ns...>::type>;
};
template <typename N>
class EqualQ<N>
{
public:
using type = ExtractQ_t<N>;
};
template <typename ...Ns>
using EqualQ_t = typename EqualQ<Ns...>::type;
int main()
{
struct Qness{};
struct Foo{using Q = Qness;};
struct Bar{using Q = Qness;};
struct Baz{using Q = Qness;};
using F = EqualQ_t<Foo,Bar,Baz>;
static_assert(std::is_same_v<F,Qness>);
return 0;
}
Yes, I realize it's not idiomatic, definitely less clean than either of Max's solutions, and doesn't answer my original question, but explores what I was trying to avoid.
One of the things I did discover doing it in this manner though was that aliases can't be specialized like class templates can. So point of education there too. I had to turn EqualQ into a templated struct.
The thing is, doing it this way wouldn't have educated me on why I couldn't unpack my parameter packs the way I originally wanted to, and certainly not to Max's idiomatic pattern, which I shall now be adopting. :)