Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use the "identity" tmp trick?

Tags:

c++

I have seen this meta function used, but never really understod why and in what context it is required. Can someone explain it with an example?

template <typename T>
struct identity
{
    using type = T;
};
like image 352
0xBADF00 Avatar asked Aug 11 '15 12:08

0xBADF00


2 Answers

Trick #1

Prevents template argument deduction:

template <typename T>
void non_deducible(typename identity<T>::type t) {}

non_deducible(1);      // error
non_deducible<int>(1); // ok

template <typename T>
void first_deducible(T a, typename identity<T>::type b) {}

first_deducible(5, 'A'); // ok

Trick #2

Disables unsafe/unwanted implicit deduction guides (c++17):

template <typename T>
struct smart_ptr {
    smart_ptr(typename identity<T>::type* ptr) {}
};

smart_ptr{new int[10]};  // error
smart_ptr<int>{new int}; // ok

Trick #3

Makes defining type-traits (and other metafunctions) easier:

template <typename T>
struct remove_pointer : identity<T> {};

template <typename T>
struct remove_pointer<T*> : identity<T> {};

Trick #4

Can be used for tag dispatching:

void foo(identity<std::vector<int>>) {}
void foo(identity<std::list<int>>) {}

template <typename T>
void bar(T t) {
    foo(identity<T>{});
}

Trick #5

Can be used for returning types:

template <int I>
constexpr auto foo() {
    if constexpr (I == 0)
        return identity<int>{};
    else
        return identity<float>{};
}

decltype(foo<1>())::type i = 3.14f;

Trick #6

Helps to specialize functions accepting a forwarding-reference:

template <typename T, typename U>
void foo(T&& t, identity<std::vector<U>>) {}

template <typename T>
void foo(T&& t) { foo(std::forward<T>(t), identity<std::decay_t<T>>{}); }

foo(std::vector<int>{});

Trick #7

Provides alternative syntax for declaring types, e.g. pointers/references:

int foo(char);
identity<int(char)>::type* fooPtr = &foo; // int(*fooPtr)(char)

identity<const char[4]>::type& strRef = "foo"; // const char(&strRef)[4]

Trick #8

Can be used as a wrapper for code expecting nested T::type to exist or deferring its evaluation:

struct A {};
struct B { using type = int; };

std::conditional<has_type<A>, A, identity<float>>::type::type; // float
std::conditional<has_type<B>, B, identity<float>>::type::type; // B

Trick #9

In the past, it used to serve as a workaround for a missing scope operator from the decltype() specifier:

std::vector<int> v;
identity<decltype(v)>::type::value_type i;
// nowadays one can say just decltype(v)::value_type

The identity utility is proposed to be added to c++20:

namespace std {
    template <typename T>
    struct type_identity { using type = T; };

    template <typename T>
    using type_identity_t = typename type_identity<T>::type;
}
like image 161
Piotr Skotnicki Avatar answered Oct 14 '22 08:10

Piotr Skotnicki


It introduces a non-deduced context, when deducing template arguments from function parameters. For example, say you have a function with the following signature :

template <class T>
void foo(T a, T b);

If someone were to call foo(123L, 123), they'd get a substitution error, as T cannot match long int and int at the same time.

If you want to match only the first argument, and convert the other one implicitly if needed, you can use identity :

template <class T>
void foo(T a, typename identity<T>::type b);

Then b does not participate in type deduction, and foo(123L, 123) resolves to foo<long int>(123L, 123).

like image 18
Quentin Avatar answered Oct 14 '22 07:10

Quentin