auto and decltype

auto


In one of the previous posts we have explored auto in detail, however there are few places where auto cannot be used. One of them happens to be the function argument and return type:

Consider some code:

auto function(auto var)
{
    return var;
}

int main()
{
    int var = function(4);
    double d = function(8.9);

    return 0;
}

Here compiler cannot deduce the argument type. As in both the case args are being passed by value, and double is 8 bytes while int is 4 bytes. This cannot work as compiler needs to allocate memory for args passed by value and they have to be of same type (or convertible) but in case of auto compiler won’t look for implicit conversion. If your intention was to write a generic function that can deduce the argument and return type then better go with templates.

decltype


decltype and auto go hand-in-hand. auto means compiler should infer the type, while decltype means I already have an expression now deduce it’s type. Now here expression can be something that is a valid statement in C++, actually that evaluates to something.

int main()
{
    int i = 0;

    //  now j is of type int
    decltype(i) j = i;

    const int& k = i;

    //  var is of type const int&
    decltype(k) var = k;

    return 0;
}

decltype does not evaluate expression, rather it checks for the type of the expression.

#include <vector>
#include <set>

std::vector<int> ToVector(const std::set<int>& vals)
{
    return std::vector<int>(vals.cbegin(), vals.cend());
}

int main()
{
    std::set<int> vals;
    
    //  here ToVector(vals) is an expression -- resulting in std::vector<int>
    //  
    decltype(ToVector(vals)) vec = ToVector(vals);

    //  also you could have written:
    //  fails to compile with error: "illegal use of this type as an expression"
    //
    decltype(ToVector(std::set<int>)) vec1 = ToVector(vals);

    //  however this would work:
    //  std::set<int>() is an expression
    //
    decltype(ToVector(std::set<int>())) vec2 = ToVector(vals);

    //  all of this can be achieved using auto as well
    //
    auto vec3 = ToVector(vals);

    return 0;
}

So what is the difference between decltype and auto? auto makes compiler infer types while decltype makes compiler evaluate type of an expression. consider: (AutoDemo class comes from post on auto)

class AutoDemo
{
public:
    AutoDemo()
    {}

    operator double()
    {
        return 8.0;
    }
};

int main()
{
    //  autoVar1 is of type AutoDemo
    auto autoVar1 = AutoDemo();

    //  autoVar2 is of type double
    //
    auto autoVar2 = static_cast<double>(autoVar1);

    //  don't have to cast explicity to get the required type
    //  here double() is an expression as it default constructs
    //  a double on the fly and it's type evaluates to double
    //  so doubleVar is of type double and perator double() kicks in
    //
    decltype(double()) doubleVar = autoVar1;

    return 0;
}

decltype unleashed


Codes described above are simple examples to demonstrate decltype functionality, lets tackle one of the C++ Meta Programming problems to see the real power of decltype with templates.

Problem: return true if the type has function push_back else false.

We are familiar with vector and list, both of these STL containers are sequential and have push_back function, while set is an associative container and does not have any function named push_back. We want to write program such that given a type we want to know if it has function named push_back or not. It should not fail to compile!

See comp.lang.c++.moderated for the problem

#include <iterator>
#include <vector>
#include <iostream>
#include <set>
#include <list>

template<class Cont>
struct has_back_insertion
{
    template <class U>
    static double test(...);

    //  this is equivalent to:
    //  
    //  static char test(void* ptr) 
    //  as vector::push_back returns void
    //  so does list::push_back
    //  
    //  have fun reading and making sense of this decltype declaration
    //
    template<class U>
    static char test(decltype(U().push_back(
                    decltype(typename std::iterator_traits<
                    decltype(U().begin())>::value_type())()))* ptr);

    enum
    {
        //  if the Cont has push_back function, then
        //  char test(void* ptr)  is preferred over double test(...);
        //  
        value = sizeof(test<Cont>(nullptr)) == sizeof(char)
    };
};


int main()
{
    //  vechasback -- true
    //
    bool vechasback = has_back_insertion<std::vector<int>>::value;

    //  sethasback -- false
    //
    bool sethasback = has_back_insertion<std::set<int>>::value;
    
    //  listhasback -- true
    //
    bool listhasback = has_back_insertion<std::list<int>>::value;

    //  doublehasback -- false
    //
    bool doublehasback = has_back_insertion<double>::value;

    std::cin.ignore();

    return 0;
}

Dissecting function matching code:

//  U is the container being passed as types dont work with decltype
//  we create an object with U()
//  we want to enquire that push_back function is availabe so 
//  call push_back on U()
//
decltype(U().push_back(decltype(typename std::iterator_traits<
decltype(U().begin())>::value_type())()))* ptr);

//  but push_back function takes in 
//  value as well so 
//  typename because we are using generic type in a template
//  then we query the value_type from iterator, that's why
//  iterator_traits
//
decltype(typename std::iterator_traits<decltype(U().begin())>::value_type())

//  but it needs iterator type 
//  and again decltype comes in picture with
//  here we U().begin() returns U::iterator
//  again decltype does not work with types so expression
//
decltype(U().begin())

I have covered decltype and auto in sufficient detail for further exploration. has_back_insertion template uses a very powerful technique, overload-resolution, for use in C++ Templates and C++ Template Metaprogramming. We will see some of these in details in later posts.

Till then have fun…