Modern (Effective) C++ - Perfect forwarding fail cases

This article is inspired from Item 30 of Effective Modern C++ by Scott Meyers.
Here we talk about the circumstances in which perfect forwarding fails.

Forwarding functions by nature are generic functions i.e template functions. To make it more generic, many forwarding functions are variadic template functions.

Template parameters not only encode Lvalue, Rvalueness, but also other important characteristics like const and volatile. .

Since perfect forwarding is pervasive in many of the C++11 features, like std::make_unique etc,we should be aware of the scenarios when forwarding might fail. These cases are listed below.

For illustration let's assume we have functions


forwardingFunc calls func by forwarding it's parameters.


Braced initializers

Suppose our function is

    void func(std::vector<int>& params)

and we call this the function func directly using func({1,2,3,4}); it compiles fine.

But if we call the function forwardingFunc like this forwardingFunc ({1,2,3,4});  , it does not compile.

This is because if we call func directly, the compiler will try to cast it to the right type and thus creates a temporary vector.

But if we call forwardFunc, and since forwardingFunc is a template function, compiler tries to deduce the parameters passed.
In this case, compilers are not able to deduce the type of {1,2,3,4}.
However if we were to do

    auto list = {1,2,3,4};  // according to item 2, auto deduces this to std::initializer_list<int>
    forwardingFunc(list);  // this compiles fine.


Using 0 or NULL

Lets say our function is
    void func(int* ptr)

In one of items covered in the book, we are asked to use nullptr instead of 0 or NULL to denote null pointers. Here is one reason to do so. When 0 or NULL is passed, the template deduces it to an int instead of null pointer. and when a int is forwarded to func (which takes int pointer and not an int), it produces compiler error.



Declaration of integral static const data members

Note that this is for only const static integral data members. Not for other data types.

Compilers usually does const propagation for such data members, and wherever it is used, it is substituted with the value (similar to macro, 23 in the below example).

Hence, the compiler does not create a memory location for these kind of variables. But the compiler does need a memory to be allocated, if we were to point to it using a pointer. And as we know, reference is also internally implemented as pointer ( automatically de-referenced pointer). In our case, we are using a reference, universal reference that is. This causes link time issues in some compilers.


But if we make a small change as shown below, the code works fine in Ubuntu as well.


If we define the static member variable, memory gets allocated to it and the link time error disappears. We can also note that, this behavior is not consistent across all compilers.But for the code to be portable across other compilers and systems, it is better to define the variable.

There are two other cases - overloaded function names and template names. and bitfields, which we will deal in a separate article


Modern (Effective) C++ - Perfect forwarding fail cases Modern (Effective) C++ -  Perfect forwarding fail cases Reviewed by zeroingTheDot on February 28, 2018 Rating: 5

No comments:

Powered by Blogger.