4

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);
};
Amir Kirsh
  • 12,564
  • 41
  • 74
  • 1
    What do you want to achieve? If you know that `T` is a type, just use `T i1`to create a variable of that type. If you know that `t` is a variable, just use `decltype(t) i2` to create a variable of the same type. – MadScientist Oct 01 '20 at 13:07
  • `type_of` function could return any type. The implementation of `type_of` is just the simplest example. – bolov Oct 01 '20 at 13:10
  • There’s 0 point to using type_identity like you do. Just use a type alias to the type you’re trying to transfer. Why pass around type_identity of an int when what you really want is to pass around the int type?? Makes no sense to me. In other words: don’t use stuff if you don’t need it. Lots of the C++ library is “on as needed basis” and should only be employed when you explicitly have a need. Here I fail to see any need at all. It’s like someone who read about design patterns and tries to choose one for everything and an enterprisey mess results. – Kuba hasn't forgotten Monica Oct 01 '20 at 13:17
  • This is just _a minimal example_, the idea is that some function should return a type (refer to my `type_of` as just an example, this is not the main issue here), the way to return a type is by using `std::type_identity` then the caller wants to use the returned type and create an object of that type. – Amir Kirsh Oct 01 '20 at 13:20
  • 1
    If you want to return a type why are you using functions that by definition return objects? In C++ types are returned from what amounts to meta functions and there’s an impedance mismatch as you see, and type_identity and similar solutions don’t do much to fix that. It’s not a fixable problem given how the language is - not until types become first-class values. Once the language allows returning types as values and then using those values wherever a type is needed, the problem will be solved. As of now, only meta functions would have less point of use burden since they are types themselves. – Kuba hasn't forgotten Monica Oct 01 '20 at 13:26
  • 1
    Are you looking for something like [this](https://stackoverflow.com/q/38462104/3233393)? – Quentin Oct 01 '20 at 13:44
  • @Quentin yes, in a way it is trying to achieve the same (wow! what a clever solution there!) but without macros. – Amir Kirsh Oct 01 '20 at 14:13

5 Answers5

2

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;

}
bolov
  • 72,283
  • 15
  • 145
  • 224
  • 1
    Sure, but the idea is to get something that is _unaware_ on the caller side whether the template parameter is a type or a variable, mainly for reasons of ease of use. – Amir Kirsh Oct 01 '20 at 13:17
2

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

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
1

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!

Amir Kirsh
  • 12,564
  • 41
  • 74
  • How does this solve the problem? The question you wrote is about *getting* the type of a value, not creating an object *based* on that type. – Nicol Bolas Oct 01 '20 at 13:46
  • 1
    @NicolBolas updated the answer to deal also with _getting the type_ in a more elegant way. – Amir Kirsh Oct 05 '20 at 11:11
0

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

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
};
Guss
  • 762
  • 4
  • 20