Universal reference parameters often have efficiency advantages, but they typically have usability disadvantages.
This solution works for overloaded
logAndAdd example in Item 26, where we break the overloaded function into two:
logAndAddIdx. However, this will not work for
Person constructor - the constructor names are fixed by the language.
This is the original function
void logAndAdd(const std::string& name) we see in Item 26. Not efficient in some cases, but works as expected.
Pass by value
According to the advice in Item 41, we may consider passing objects by value when we know we’ll copy them. Thus, the
Person example may get revised like this:
With this design,
int-like arguments get passed to
int overload, and arguments of type
std::string (and anything from which
std::string could be created, e.g., literals) get passed to the
Use Tag dispatch
Add another “tag” parameter to help compiler differentiate the overloading cases as we want:
false are runtime values, and what we need here for the tag parameter should be compil-time types that corresponds to
false, which in the Standard Library are called
std::false_type. This compile-time variables serve no purpose at runtime, so some compilers who’s smart enough may recognize these tag parameters and optimize them out of the program’s execution image.
Tag dispatch is a standard building block of template metaprogramming to let the tag determine which overload gets called, so that overloading on universal references may work as expect.
enable_if to constrain templates that take universal references
Tag dispatch solves some of the problems related with templates taking universal references, but not all of them. The perfect-forwarding constructor for the
Person class, for example, remains problematic: even if we write only one constructor and apply tag dispactch technique to it, some constructor calls (copy from
const vs non-
const lvalues) may sometimes be handled by compiler-generated functions (e.g., copy and move constructors) that bypass the tag dispatch system.
Thus, we want to constrain on when the function template is permitted to be employed. By default, all templates are enabled, but a template using
std::enable_if is enabled only if the condition specified by
std::enable_if is satisfied.
In the case of
Person's perfect forwarding constructor, we want to enable its instantiation only if the type being passed isn’t
Person, so that the class’s copy or move constructor my handle the calls where a
Person object gets passed in. Specifically, when checking the type of the argument being passed, we want to ignore its referenceness, constness, and volatileness using
Moreover, if we want to make sure the derived class work properly, the conditions for
std::enble_if get more restricted: we want to enable it for any argument type other than
Person or a type derived from
Person. To determian whether one type is derived from another, we can use
In C++14, by employing alias templates, we can save some typing for
Finally, to get our perfect forwarding constructor to work with the
int overload, we have to add another constrain in
std::enbale_if to check the integral arguments type:
To make even more effective code, considering some kinds of arguments can’t be perfect-forwarded, as well as the fact that forwarding functions tend to create lengthy error messages, which is debug-unfriendly, we can use
static_assert, accompanied with
std::is_constructible, to perform a compile-time test to determine whether an object of one type can be constructed from an object (or a set of objects) of a different type (or set of types):