Recent posts

Determining types deduced by the compiler in C++


Posted by Diego Assencio on 2017.08.23 under Programming (C/C++)

Suppose you are debugging a C++ program and need to know what is the type which the compiler deduces for a certain expression. There are many complicated ways of doing this, but in this post, I will show you a very simple trick which allows you to determine types directly at compile time.

The trick is this: declare a class template but do not define it, then attempt to instantiate this class template with the expression whose type you are trying to determine. Here is an example:

/* class template declaration (no definition available) */
template<typename T>
class ShowType;

int main()
{
    signed int x = 1;
    unsigned int y = 2;

    /* (signed int) + (unsigned int): what is the resulting type? */
    ShowType<decltype(x + y)> dummy;

    return 0;
}

In the code above, we are trying to determine the type deduced by the compiler when we add a signed int (x) and an unsigned int (y). This type is decltype(x + y). When the compiler attempts to create an instance of ShowType<decltype(x + y)>, it realizes this is not possible and indicates the problem with a very helpful error message:

error: aggregate ‘ShowType<unsigned int> dummy’ has incomplete type and cannot
be defined

In this message, the compiler (in my case, gcc) is telling us that it tried to create an instance of ShowType<unsigned int> but failed at it. Therefore, the type of the expression x + y is decltype(x + y) = unsigned int. This ŕesulting type comes directly from the integer addition rules specified in the C++ language.

Let's try a more interesting example. In C++, the type deduction rules for template parameters are complex. When in doubt, you can use the trick above to determine which type is deduced by the compiler for a certain template parameter:

/* class template declaration (no definition available) */
template<typename T>
class ShowType;

template<typename T>
void my_function(T x)
{
    ShowType<T> dummy;

    /* do something with x */
}

int main()
{
    const int x = 3;
    my_function(x);

    return 0;
}

One common doubt which developers often have regarding the type T on my_function is: will it be deduced to be int or const int? As it turns out, since we are passing x by value, the compiler will deduce T to be int:

error: ‘ShowType<int> dummy’ has incomplete type

As a final example, let's consider auto. The rules for auto type deduction are usually the same as the ones for template types, but auto type deduction assumes that initializing expressions such as {1,2,3} represent initializer lists. Let's show that in practice:

#include <initializer_list>

template<typename T>
class ShowType;

int main()
{
    auto x = {1,2,3};

    ShowType<decltype(x)> dummy;

    return 0;
}

The error message tells us what we expect:

error: aggregate ‘ShowType<std::initializer_list<int>> dummy’ has incomplete
type and cannot be defined

Notice that we need to pass an actual type (not an expression) to ShowType<T>, so in the examples above in which we wanted to determine the type of a certain expression (e.g. x + y), we needed to enclose the expression with decltype. On the second example, we already had the desired type T available on the definition of my_function, so we could use it directly.

Comments (0) Direct link

How to fix broken MathJax fonts on Linux


Posted by Diego Assencio on 2017.08.09 under Linux (General)

If you are using Linux and your MathJax fonts look ugly, and if you are certain that MathJax is correctly configured on the webpage you are accessing (e.g. by checking that things look fine on another device or browser), then your browser is probably selecting STIX fonts which are installed on your system instead of the ones offered by MathJax. The simplest way to solve this problem is by removing these STIX fonts. On Ubuntu/Debian, this can be done with the following command:

sudo apt remove fonts-stix

You can now see if this was the root cause of the problem by reloading the affected webpage with Ctrl+F5 (this will force your browser to bypass its cache).

Comments (0) Direct link

How std::move breaks return value optimization


Posted by Diego Assencio on 2017.08.07 under Programming (C/C++)

Consider the following program:

std::vector<int> random_numbers(const size_t n)
{
    std::vector<int> numbers;

    /* generate n random numbers, store them on numbers */

    return numbers;
}

int main()
{
    /* create an array with 100 random numbers */
    std::vector<int> my_numbers = random_numbers(100);

    ...
}

Returning an std::vector<int> by value can make a developer nervous: will this cause the vector to be copied? That would be a costly performance penalty and therefore something to be avoided, if possible.

In this type of situation, a solid understanding of how return value optimization (or simply RVO, for short) works leads to inner peace. Any decent compiler will understand that numbers is a temporary variable whose value will be returned at the end of the random_numbers function, and since this returned value is an rvalue (a temporary object, see line 13), it can be used to initialize my_numbers directly, i.e., without going through any of the std::vector<int>'s constructors. In other words, no vectors should be copied or moved on the code above. How wonderful!

Let's take a closer look at how RVO removes the need for a copy operation. When random_numbers is called, memory on the call stack will be reserved for its return value (an std::vector<int>). Inside of random_numbers, numbers is clearly a temporary variable whose value will be returned at the end, so instead of copying this value to the memory location reserved for the function's return value at the very end, numbers is stored directly at that memory location. The elimination of such a copy operation when a function returns is what "return value optimization" stands for. In general, any type of optimization which causes copy operations to be eliminated is referred to as a copy elision optimization.

Despite all these facts, it is not uncommon for developers to be less confident than they should in this type of situation and prefer "being on the safe side" by writing this type of code:

std::vector<int> random_numbers(const size_t n)
{
    std::vector<int> numbers;

    /* generate n random numbers, store them on numbers */

    return std::move(numbers);   /* don't do this! */
}

As innocent as this decision may be, it is severely flawed because of the way RVO works: only a local variable or a temporary object can be stored directly at the memory location for a function's return value (function parameters are not eligible for that), and this is only allowed if such an object is directly returned by the function and has the same type as the function's return type. By adding std::move on the return statement above, we converted the type of the returned object to std::vector<int>&& (an rvalue reference to an std::vector<int>), but random_numbers returns an std::vector<int>. This violates the conditions required for RVO, and the compiler will have no choice but to make numbers be constructed outside the memory area reserved for random_numbers's return value and then moved to that location when the function returns (a move operation is still possible here since std::move(numbers) is an rvalue and will therefore trigger the move constructor for the std::vector<int> which is constructed as the return value).

For many user-defined types, such an additionally incurred move operation will be cheap, but it will definitely not be cheaper than what RVO offers and therefore, by "playing safe", we ended up inevitably pessimizing our program. Also, notice that we were lucky to have a return type (std::vector<int>) with a move constructor; had this not been the case, the added std::move would have caused the return value to be initialized through a copy constructor, which is what we wanted to avoid in the first place. Ouch!

To finalize, here is an example which involves all concepts discussed so far:

#include <iostream>

class X
{
public:
    /* default constructor */
    X() { std::cout << "X::X()\n"; }

    /* copy constructor */
    X(const X&) { std::cout << "X::X(const X&)\n"; }

    /* move constructor */
    X(X&&) { std::cout << "X::X(X&&)\n"; }
};

X good()
{
    std::cout << "good()\n";

    X x;
    return x;
}

X bad()
{
    std::cout << "bad()\n";

    X x;
    return std::move(x);
}

int main()
{
    X x1 = good();
    X x2 = bad();

    return 0;
}

The program's output illustrates how the std::move on the bad function disables RVO and forces an unnecessary move operation (this will be the case even if you compile with lots of optimizations enabled, e.g. by using -O3 on gcc):

good()
X::X()
bad()
X::X()
X::X(X&&)

Try compiling and running this program, then remove the move constructor from X. The resulting output shows that std::move now causes X to be copied:

good()
X::X()
bad()
X::X()
X::X(const X&)
Comments (1) Direct link